Chuyển đến nội dung
Trở lại Nymbot

Cơ sở tri thức Nhà phát triển

Cân bằng, top-ups và key

Kiểm tra những gì bạn có, tăng lên trên Lightning, tăng lên tự động từ ví của riêng bạn, xem mỗi yêu cầu có giá bao nhiêu và quản lý các phím từ mã.

Kiểm tra cân bằng

Cả hai cân bằng của bạn, và bao nhiêu của mũ khóa này được sử dụng.

GET https://nymbot.ai/api/v1/credits/balance - Cần có API key POST Nó cũng hoạt động, cho khách hàng mong đợi nó.

balance là hai số dư cùng nhau bằng đô la ở mức giá Bitcoin hiện tại, đối với các công cụ mong đợi một số duy nhất (null nếu giá không thể đọc được). phần còn lại là trong tín dụng và lãi suất, đó là cách số dư thực sự được giữ. key Mô tả khóa đã yêu cầu.Một khóa đã đạt đến mốc của nó vẫn có thể kiểm tra số dư.

Trả lời

{
  "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"
  }
}
StatusKhi nào
401Chìa khóa bị mất, không rõ, bị thu hồi hoặc hết hạn.

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

Phương thức thanh toán

Làm thế nào bạn có thể top up, và giới hạn. Lightning là phương pháp duy nhất.

GET https://nymbot.ai/api/v1/topup/payment-methods Không cần chìa khóa

Một top-up là 10 đến 1.000.000 sats; một Pro top-up phải mua ít nhất một tín dụng Pro, vì vậy nó bắt đầu ở 100 sats. bulk_bonus Danh sách các khoản tín dụng bổ sung trên top-ups lớn hơn, giống như trong ứng dụng: 10%, 15% hoặc 20% thêm trên top-ups tiêu chuẩn từ 500, 1.000 hoặc 5.000 sats, và trên top-ups Pro từ 5.000, 10.000 hoặc 50.000 sats.

Trả lời

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

Lên trên đỉnh Lightning

Tạo một hóa đơn Lightning thêm tín dụng cho nym mà chìa khóa thuộc về. trả nó từ bất kỳ ví Lightning, sau đó Kiểm tra nó để có thêm tín dụng.

POST https://nymbot.ai/api/v1/topup/create/btc-lightning - Cần có API key

trườngLoạiCầnMô tả
amountSốĐúngBao nhiêu, trong currencyMột số toàn bộ cho sats.
currencyStringKhôngSATS (Đối với sự thất bại), USD hoặc BTCĐồng đô la được chuyển đổi theo giá Bitcoin hiện tại.
tierStringKhôngpro (the default) hoặc standard: cân bằng mà khoản tín dụng đi đến.

Một tín dụng tiêu chuẩn là 10 sats và một tín dụng Pro 100 sats, cộng với bất kỳ phần thưởng lớn nào; credits nói những gì hóa đơn này sẽ thêm.

Trả lời

{
  "invoice_id": "b7d41e0c95a2f38e6c1d0e9a4b7f2c61d3e8a05f9b2c4d7e1a6f3b8c0d5e2a9f4",
  "payment_request": "lnbc100u1p5...",
  "amount_sats": 10000,
  "credits": 115,
  "tier": "pro",
  "expires_at": "2026-09-30T09:27:00Z",
  "status": "pending"
}
StatusKhi nào
400Một phương pháp khác trên con đường (unsupported_method, một loại tiền tệ chưa biết (unsupported_currency) hoặc cấp, một số tiền thiếu, hoặc một số tiền dưới mức tối thiểu (amount_too_small(Hơn 1 triệu lượt xem)amount_too_large) hoặc bị từ chối bởi ví Lightning (amount_out_of_range).
429Hơn 60 hóa đơn cho nym này, hoặc 120 từ địa chỉ này, trong một giờ (rate_limit_exceeded, với Retry-After).
502Hiện tại chưa có tin nhắn trong hồ sơ của bạn (invoice_unavailable, với 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);

Kiểm tra top-up

Hỏi nếu hóa đơn đã được thanh toán và, một khi nó đã có, thêm tín dụng. Kiểm tra là những gì tín dụng nó, vì vậy sau khi thanh toán, kiểm tra cho đến khi trạng thái là creditedKiểm tra lại sau đó là an toàn: tín dụng hạ cánh một lần, bất kể bao nhiêu lần bạn yêu cầu.

GET https://nymbot.ai/api/v1/topup/status/{invoice_id} - cần một chìa khóa từ nym tạo ra hóa đơn.

status là pending (Chưa được thanh toán) paid (đã thanh toán nhưng chưa được ghi nhận; kiểm tra lại), credited (Về sự cân bằng của bạn) hoặc expired (không thanh toán kịp thời).Các trường cân bằng là cho tầng mà hóa đơn đứng đầu.

Trả lời

{
  "invoice_id": "b7d41e0c95a2f38e6c1d0e9a4b7f2c61d3e8a05f9b2c4d7e1a6f3b8c0d5e2a9f4",
  "status": "credited",
  "amount_sats": 10000,
  "credits": 115,
  "tier": "pro",
  "expires_at": null,
  "balance_credits": 523.33,
  "balance_sats": 52333
}
StatusKhi nào
400ID không phải là ID 64 ký tự từ cuộc gọi tạo.
404Không có tài khoản bởi id đó cho nym của bạn (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);

muốn lịch sử

Một hàng cho mỗi yêu cầu: nó là gì, mô hình nào, bao nhiêu token và chi phí của nó. Không có lời nhắc hoặc câu trả lời được giữ lại, vì vậy không có trả lại. hàng được giữ trong 90 ngày, mới nhất đầu tiên. một khóa đã đạt đến giới hạn của nó vẫn có thể đọc lịch sử của nó.

GET https://nymbot.ai/api/v1/queries/history — cần một phím API, nhìn thấy yêu cầu của riêng mình, hoặc một Yêu cầu ký từ nym của bạn, người nhìn thấy mỗi chìa khóa.

trườngLoạiCầnMô tả
pageTổng quát (query)KhôngMặc định 1, tối đa là 1000; một trang cao hơn là 400 invalid_value.
page_countTổng quát (query)KhôngRanh giới mỗi trang. mặc định 20, tối đa là 100.
start_date
end_date
Lời bài hát: string (query)KhôngISO 8601 ngày hoặc thời gian
modelLời bài hát: string (query)KhôngChỉ có mô hình này.
typeLời bài hát: string (query)Khôngchat, responses, messages, image, video, speech, transcription hoặc embedding.
all_keysLời bài hát: Boolean (query)KhôngVới một chìa khóa: true bao gồm tất cả các phím của cùng một nym.Default false.
key_idLời bài hát: string (query)KhôngVới một yêu cầu được ký, hoặc với all_keys=trueChỉ có chìa khóa này.

Trả lời

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

Đăng ký tài khoản yêu cầu

Tạo, thay đổi và thu hồi phím, tóm tắt tài khoản và tự động top-ups không lấy một phím API. Họ lấy một chữ ký từ nym của bạn, vì vậy một khóa bị rò rỉ có thể chi tiêu lên đến cap của nó nhưng không bao giờ có thể làm cho một phím khác hoặc nâng cap của riêng mình.

Ứng dụng làm điều này cho bạn: mọi thứ trong Lửa sheet sử dụng các điểm cuối này. bạn chỉ cần phần này để quản lý các phím từ mã của riêng bạn.

Chữ ký là một sự kiện Nostr loại 27235 (NIP-98), được gửi base64 mã hóa trong Authorization Header với từ Nostr Ở phía trước:

Sự kiện

{
  "kind": 27235,
  "created_at": 1790726400,
  "tags": [
    ["u", "https://nymbot.ai/api/v1/keys"],
    ["method", "POST"],
    ["nonce", "9c4e21f07a3b...16 random bytes in hex"],
    ["payload", "3f1a0d7c8e2b...sha256 of the exact request body in hex"]
  ],
  "content": "",
  "pubkey": "your public key in hex",
  "id": "...",
  "sig": "..."
}
  • u là URL đầy đủ của yêu cầu, chuỗi truy vấn được bao gồm, chính xác như đã gửi.
  • method Đó là phương pháp HTTP.
  • payload là SHA-256 của cơ thể yêu cầu thô, trong hex. POST Và PATCH, và cơ thể bạn gửi phải là byte cho byte mà bạn đã hash.
  • created_at phải nằm trong vòng 60 giây của đồng hồ máy chủ.
  • Mỗi sự kiện hoạt động một lần, a GET bao gồm, vì vậy một tiêu đề đã chụp không thể được phát lại. ký một tiêu đề mới cho mỗi yêu cầu. Thêm một nonce Tag với một giá trị ngẫu nhiên để hai yêu cầu được ký trong cùng một giây vẫn khác nhau.
  • Cơ thể của một yêu cầu đã ký có thể tối đa là 64 KB, và một cơ thể cần Content-Type: application/json.

Sự kiện bị mất trở lại 401 missing_nostr_authMột trong đó được hình thành kém, chữ ký kém, quá cũ, hoặc cho một URL khác nhau, phương pháp hoặc cơ thể trả về invalid_nostr_auth, với lý do trong thông điệp; một tái sử dụng một trở lại nostr_auth_replayedChữ ký được kiểm tra trước khi cơ thể được đọc, và mỗi địa chỉ có thể thất bại 30 lần một phút (một địa chỉ IPv6 được tính là toàn bộ /64); sau đó nó nhận được 429 Với Retry-After.

Các trình duyệt chỉ có thể gọi các điểm cuối này từ các trang web của Nymbot (https://nymbot.ai, https://nymchat.app Một trang trên bất kỳ trang nào khác không nhận được tiêu đề CORS trở lại, vì vậy nó không thể đọc những gì họ trả về. OriginHọ không bị ảnh hưởng.

Chìa khóa bí mật của bạn

Chữ ký cần chìa khóa bí mật của nym của bạn (the nsecChỉ đặt nó vào một kịch bản trên một máy bạn tin tưởng, đọc nó từ môi trường thay vì viết nó vào tệp, và thích ứng dụng khi bạn có thể.

Những trợ lý này xây dựng tiêu đề. Các ví dụ sau trên trang này sử dụng chúng. Họ đọc khóa bí mật trong hex từ NOSTR_SECRET_HEX; cURL one sử dụng Nak công cụ dòng lệnh, lấy một phím nsec hoặc hex, và sha256sum Trên 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");
}

Tài khoản Summary

Những gì trang API của ứng dụng hiển thị ở phía trên cùng: khóa công cộng của bạn, cả hai cân bằng, bao nhiêu phím đang hoạt động (không bị thu hồi hoặc hết hạn), và Tự động top-up cài đặt, hoặc null khi máy chủ không cung cấp.

GET https://nymbot.ai/api/v1/account cần a Yêu cầu ký.

Trả lời

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

Quản lý Key

Các điểm cuối đằng sau danh sách khóa của ứng dụng. Tất cả chúng đều cần một Yêu cầu kýMỗi phím được trả về theo hình thức này, với thời gian trong ISO 8601 và số tiền trong sats:

đối tượng chính

{
  "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 Nó là đủ để nhận ra một phím nhưng không sử dụng nó. phím chính nó chỉ được trả lại một lần, khi nó được thực hiện.

Danh sách key

GET https://nymbot.ai/api/v1/keys - Đã ký

trườngLoạiCầnMô tả
include_revokedLời bài hát: Boolean (query)KhôngBao gồm các phím bị thu hồi.Default false.

Trả lời

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

Tạo một chìa khóa

POST https://nymbot.ai/api/v1/keys Đã ký - Trở lại 201.

trườngLoạiCầnMô tả
nameStringĐúng1 đến 40 ký tự, khác với các phím hoạt động khác của bạn (bỏ qua trường hợp).
limit_satsintegerKhôngTỷ lệ chi tiêu trong sats, ít nhất 1. để nó ra không có cap.
reset_periodStringKhôngdaily, weekly hoặc monthlynhu cầu limit_satsĐể nó ra cho một mũ không bao giờ reset.
expire_atstring hoặc integerKhôngKhi chìa khóa ngừng hoạt động: một ISO 8601 thời gian, hoặc milliseconds kể từ 1970.

Trả lời (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"
  }
}
StatusKhi nào
400Một cái tên bị thiếu hoặc quá dài; một cái tên đã được sử dụng (duplicate_name( ); một cap không phải là một số nguyên ít nhất là 1; một khoảng thời gian reset không có cap; một thời gian hết hạn trong quá khứ; một trường không biết (unknown_parameter); hoặc 25 phím đang hoạt động (too_many_keys).
429Hơn 60 phím được thực hiện bởi nym này, hoặc 120 từ địa chỉ này, trong một giờ (rate_limit_exceeded, với 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);

Đọc 1 key

GET https://nymbot.ai/api/v1/keys/{id} - Đã ký

Trở về {"data": {…}} với đối tượng chính, hoặc 404 key_not_found Nếu không có chìa khóa của bạn có ID đó.

cURL

URL=https://nymbot.ai/api/v1/keys/4f0c9a1be27d3856
curl "$URL" -H "Authorization: $(nostr_auth GET "$URL")"

Python

import requests

url = "https://nymbot.ai/api/v1/keys/4f0c9a1be27d3856"
print(requests.get(url, headers={"Authorization": nostr_auth("GET", url)}).json()["data"])

JavaScript

const url = "https://nymbot.ai/api/v1/keys/4f0c9a1be27d3856";
console.log((await (await fetch(url, { headers: { "Authorization": nostrAuth("GET", url) } })).json()).data);

Thay đổi key

PATCH https://nymbot.ai/api/v1/keys/{id} - Đã ký

Gửi bất kỳ name, limit_sats, reset_period Và expire_at, với các quy tắc tương tự như khi tạo một chìa khóa. null Chọn một trường: không có cap, không có reset, không có hết hạn. Thay đổi thời gian reset bắt đầu một khoảng thời gian mới ở 0. Chọn khóa bị thu hồi không thể thay đổi (400 key_revoked) Trở lại {"data": {…}} với đối tượng khóa được cập nhật.

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

Hủy bỏ chìa khóa

DELETE https://nymbot.ai/api/v1/keys/{id} - Đã ký

Ngăn chặn khóa ngay lập tức, cho tốt. Nó vẫn ở trong danh sách với revoked_at thiết lập, và có thể được nhìn thấy với include_revoked=trueHủy bỏ một phím đã bị hủy trả lời theo cách tương tự. chỉ có 50 phím mới nhất bị hủy bỏ được giữ lại; những phím cũ bị xóa khi một phím khác bị hủy bỏ.

Trả lời

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

Đánh giá NWC Auto-top-up

Kết nối một ví Lightning với Nostr Wallet Connect và Nymbot tự lên cân bằng khi chi tiêu API chạy nó thấp. Yêu cầu ký.

Làm thế nào nó hoạt động: sau khi một yêu cầu API được tính vào số dư bạn đã chọn để xem, nếu số dư đó đã giảm xuống dưới ngưỡng của bạn, Nymbot thực hiện hóa đơn cho số tiền lên trên của bạn, yêu cầu ví của bạn thanh toán nó, và thêm tín dụng. Nó lên đến tối đa một lần mỗi 5 phút cho mỗi nym và số dư, vì vậy một vụ nổ các yêu cầu không thể làm rò rỉ ví. chi tiêu trong các ứng dụng không kích hoạt nó. Thời gian và kích thước của top-up cuối cùng, và lỗi cuối cùng, là trong cài đặt; nếu một khoản thanh toán đi qua sau khi một lỗi, kiểm tra hóa đơn của nó với Tình trạng top-up tín dụng nó.

Trước khi kết nối ví

Một chuỗi kết nối cho phép bất cứ ai nắm giữ nó yêu cầu ví của bạn thanh toán.Nymbot lưu trữ nó mã hóa và chỉ sử dụng nó để thanh toán hóa đơn trên cùng của riêng mình, nhưng tạo một kết nối chỉ cho điều này, với ngân sách chi tiêu trong ví của bạn, vì vậy nhiều nhất nó có thể bao giờ trả là một số bạn chọn. pay_invoice.

Kết nối ví

POST https://nymbot.ai/api/v1/nwc-auto-topup/connect - Đã ký

trườngLoạiCầnMô tả
nwc_urlStringĐúngCấu trúc kết nối, bắt đầu nostr+walletconnect://Nymbot yêu cầu ví get_info trước khi lưu nó, và lưu trữ nó mã hóa.
threshold_satsintegerĐúngTop up khi số dư giảm xuống dưới số tiền này. ít nhất là 1.000.
topup_satsintegerĐúngBao nhiêu để thêm mỗi lần. 1.000 đến 1.000.000 sats.
tierStringKhôngpro (the default) hoặc standard: sự cân bằng để xem và top up.

Trả lời

{
  "data": {
    "connected": true,
    "threshold_sats": 5000,
    "topup_sats": 20000,
    "tier": "pro",
    "last_topup_at": null,
    "last_topup_sats": null,
    "last_error": null
  }
}
StatusKhi nào
400Không có kết nối string (invalid_nwc_url); ví không trả lời về chuyển tiếp của nó (nwc_unreachable) hoặc từ chối kiểm tra (nwc_rejected); kết nối không thể thanh toán hóa đơn (nwc_missing_permission); hoặc một số tiền vượt quá giới hạn.
501Tự động top-up không được bật cho máy chủ này (nwc_unavailableĐiều tương tự cũng áp dụng cho hai điểm cuối khác.

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

Đọc các Settings

GET https://nymbot.ai/api/v1/nwc-auto-topup - Đã ký

Trả về cùng một đối tượng như kết nối, với connected: false Và các lĩnh vực khác null khi không có ví nào được kết nối. chuỗi kết nối không bao giờ được trả về.

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

Tắt kết nối

DELETE https://nymbot.ai/api/v1/nwc-auto-topup/connection - Đã ký

Xóa chuỗi kết nối được lưu trữ. Không thêm top-ups được thực hiện. Để chắc chắn, bạn cũng có thể thu hồi kết nối trong ví của bạn.

Trả lời

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

Thanh toán theo yêu cầu mà không có chìa khóa

Các điểm cuối giá cố định có thể được thanh toán cho một yêu cầu tại một thời điểm trên Lightning, không có chìa khóa, không tài khoản và không có số dư: POST /images/generations, POST /images/edits, POST /videos, POST /audio/speech, POST /audio/transcriptions, POST /audio/translations Và POST /embeddingsChat, Trả lời và Tin nhắn luôn cần một chìa khóa. Một yêu cầu mang một chìa khóa được tính vào số dư như bình thường; dòng thanh toán chỉ bắt đầu khi không có chìa khóa được gửi.

Nymbot nói hai phiên bản của cùng một ý tưởng, từ một nền tảng: Lightning Labs L402 (cũng được chấp nhận dưới tên cũ của nó, LSAT) và dự thảo IETF Thanh toán HTTP Authentication với lightning Phương pháp và charge Sử dụng bất cứ điều gì khách hàng của bạn hiểu.

Chạy máy chủ của riêng bạn

Thanh toán mà không có chìa khóa chỉ được bật khi API_L402_SECRET chứa ít nhất 32 byte ngẫu nhiên, như hex (64 ký tự) hoặc base64 (44). openssl rand -hex 32Một giá trị ngắn hơn hoặc có thể đoán được tắt tính năng và ghi lại lý do tại sao. API_L402_SECRET_PREVIOUS trong một ngày: thông tin xác thực, URL trạng thái và thách thức được thực hiện theo nó tiếp tục hoạt động cho đến khi hết hạn.

thách thức

Gửi yêu cầu với No Authorization header. nếu nó là hợp lệ, không có gì chạy, và bạn có 402 Payment Required với một hóa đơn cho chính xác những gì yêu cầu chi phí: cùng một giá một khóa sẽ trả, chuyển đổi tại 10 sats một tín dụng tiêu chuẩn hoặc 100 sats một tín dụng Pro và làm tròn lên đến một toàn bộ sat (ít nhất 1 sat, và ít nhất là 0.05 tín dụng tối thiểu). WWW-Authenticate Thách thức đối với cùng một tài khoản:

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

Thanh toán request Các thông số sau đây là base64url JSON: {"amount":"237","currency":"sat","methodDetails":{"invoice":"lnbc...","network":"mainnet","paymentHash":"..."}}.

Một thách thức gắn liền với điểm cuối, đến Content-Type (loại phương tiện truyền thông của nó và, đối với đa phần, giới hạn của nó) và đến SHA-256 của cơ thể chính xác byte bạn đã gửi, và kéo dài 15 phút. Tương tự Yêu cầu lần nữa: tương tự Content-Type và cùng một byte JSON, hoặc cho các điểm cuối đa phần (/images/edits, /audio/transcriptions, /audio/translationsHầu hết các thư viện HTTP chọn một ranh giới mới mỗi khi họ mã hóa một biểu mẫu, vì vậy mã hóa nó một lần và gửi các byte đó hai lần.

Mỗi địa chỉ có thể yêu cầu 30 thách thức mỗi phút (một địa chỉ IPv6 được tính là toàn bộ /64). Các yêu cầu có địa chỉ không được biết chia sẻ một 10 nghiêm ngặt mỗi phút, và có một giới hạn tổng thể về các vấn đề thách thức Nymbot trên tất cả các địa chỉ; một yêu cầu bị từ chối trước khi một thách thức được thực hiện (ví dụ với một cơ thể không hợp lệ JSON) không được tính đối với nó. 429 Với Retry-After, được gửi trước khi cơ thể được đọc. điểm cuối trả tiền được gọi mà không có khóa hoặc chứng chỉ cũng được tính đến giới hạn chung của 120 yêu cầu không xác thực một phút cho mỗi địa chỉ. một chứng chỉ được kiểm tra trước khi cơ thể được đọc, và một yêu cầu mà Content-Type Không phải application/json (hoặc multipart/form-data Đối với upload) bị từ chối với 415 Và không bao giờ nhận được hóa đơn.

Embeddings được định giá từ một ước tính của các token trong đầu vào, với một margin 1.5 lần, vì số tiền thực tế chỉ được biết sau đó. Trả lại token.

Gửi thanh toán

Thanh toán hóa đơn với bất kỳ ví Lightning. ví cung cấp cho bạn hình ảnh trước, 64 ký tự hex. Sau đó gửi yêu cầu tương tự với một trong những điều này:

Chương trìnhHeader
L402Authorization: L402 <macaroon>:<preimage> (LSAT cũng làm việc)
Thanh toánAuthorization: Payment <base64url JSON>, nơi JSON là {"challenge": {every parameter of the challenge, as sent}, "payload": {"preimage": "<hex>"}}

Một yêu cầu trả tiền trả lời chính xác như một yêu cầu được thực hiện với một chìa khóa, ngoại trừ rằng nymbot Object không có trường cân bằng: {"payment": "l402", "tier": "pro", "paid_sats": 237, "charged_sats": 237}, và không có X-Nymbot-Balance-Sats header. Một yêu cầu thanh toán với chương trình thanh toán cũng nhận được một Payment-Receipt header (base64url JSON với challenge id, thanh toán hash như reference, status Và timestampYêu cầu trả tiền không được gắn với bất kỳ nym nào, vì vậy chúng không xuất hiện trong lịch sử truy vấn.

StatusKhi nào
402 payment_already_usedMỗi thanh toán trả cho một yêu cầu. câu trả lời là một thách thức mới cho yêu cầu này, vì vậy một khách hàng lưu trữ bộ nhớ đệm xác thực cuối cùng của mình (như lnget Do) đơn giản là đất lại một lần nữa.
402 payment_mismatchChứng chỉ đã được phát hành cho một điểm cuối khác, Content-Type hoặc cơ thể, hoặc trả ít hơn yêu cầu này bây giờ chi phí. một thách thức mới cho yêu cầu này đi kèm với nó; nếu thanh toán là quá nhỏ, những gì bạn đã trả trở lại như một Trả lại token (refund_token Và refund_sats trong cơ thể)
402 payment_expiredHơn 15 phút đã trôi qua kể từ khi thử thách. một thách thức mới đi kèm với nó. Nếu hình ảnh trước cho thấy bạn đã trả tiền, những gì bạn đã trả sẽ trở lại như một Trả lại token (refund_token Và refund_sats trong cơ thể), một lần; tín dụng sau đó được sử dụng.
401 invalid_preimageHình ảnh trước không hash đến hash thanh toán của hóa đơn.
401 invalid_payment_credentialGiấy chứng nhận bị lỗi, đã được thay đổi sau khi Nymbot phát hành nó, hoặc tên một hash thanh toán Nymbot chưa bao giờ phát hành hóa đơn cho. Một macaroon với một cảnh báo Nymbot không biết, hoặc với cảnh báo mâu thuẫn, được từ chối.
429 rate_limit_exceededHơn 30 thông tin xác thực hoặc khóa không thể xác minh đến từ địa chỉ này trong một phút, hoặc một token hoàn lại đã được gửi hơn 60 lần trong một phút. 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"));

Khách hàng xây dựng trên mppx với một phương pháp Lightning xử lý các thách thức thanh toán bản thân; chỉ cho họ ở điểm cuối và để họ trả tiền.

Video

a trả tiền POST /videos Trả lời 202 Như một chìa khóa, cộng với a status_url: GET Nó được ký và hoạt động trong 24 giờ, miễn là công việc được giữ.

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

Giữ các refund_token Từ bài viết này bạn có thể tham khảo thêm: WEB WEB WEB WEB WEB WEB WEB WEB WEB (GET /api/v1/l402/refunds Trả lời "status": "pending"); nếu render không được khấu trừ, thanh toán sẽ hạ cánh trên đó. refund_sats Đối với một công việc được hoàn lại nhưng không bao giờ token, vì vậy chia sẻ URL trạng thái không chia sẻ hoàn lại.

Trả lại

Nếu một yêu cầu trả tiền thất bại và nhà cung cấp tính phí Nymbot cho nỗ lực, thanh toán được giữ lại và lỗi nói như vậy, với charged_sats, chính xác như đối với một yêu cầu khóa. Nếu nó thất bại mà không được thanh toán, lỗi mang một Trả lại token Giá trị những gì bạn đã trả:

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

Các bộ phận chưa sử dụng trở lại theo cùng một cách: nếu bạn yêu cầu hai bức ảnh và một bức ảnh không được thanh toán, câu trả lời thành công nymbot đối tượng mang một token hoàn lại cho một thiếu; một bản sao mà chiều dài không thể đọc trước được định giá cho dài nhất có thể tập tin (không bao giờ quá 30 phút), và sự khác biệt với chiều dài thực sự trở lại như một token hoàn lại; nếu nó hóa ra là dài hơn 30 phút, nó được từ chối với 413 và toàn bộ khoản thanh toán được trả lại. Embeddings trả về những gì ước tính được giữ lại. Một video thất bại trả lại cho mã thông báo gửi của nó trả lại.

Một token hoàn tiền là một mã ngẫu nhiên 256-bit. Nymbot chỉ lưu trữ hash của nó, và nó hết hạn sau 30 ngày. Nó giữ một số dư sat, và bạn có thể:

  • trả bằng nó. Gửi Authorization: Bearer REFUND-… Một trong những điểm cuối trên (một SDK OpenAI lấy nó làm phím API của nó). giá xuất phát từ mã thông báo và những gì còn lại ở đó (refund_token_sats Trong the nymbot Một token có giá trị ít hơn yêu cầu trả lời 402 refund_insufficientMột thất bại không tính toán đặt sats trở lại trên cùng một token.
  • Kiểm tra nó. GET /api/v1/l402/refunds Với cùng một header trở lại {"sats": 237, "status": "open", "expires_at": "..."}.
  • Một token có thể được sử dụng tối đa 60 lần mỗi phút.
  • Di chuyển nó đến một nym. Đặt nó vào Trả lại quà tặng trong ứng dụng Nymbot, hoặc gọi POST /api/v1/l402/refunds/redeem Với a Yêu cầu ký Và {"refund_token": "REFUND-...", "balance": "standard"} (hoặc "pro"Toàn bộ tín dụng đi vào số dư (10 sats mỗi trên tiêu chuẩn, 100 trên Pro); sats không làm cho toàn bộ tín dụng ở lại trên token cho các yêu cầu 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"

Trả lời

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