DamageBDD NWC API Manual

DamageBDD NWC API Manual

This manual describes the authenticated NWC API exposed by DamageBDD for creating, inspecting, crediting, and revoking ledger-backed Nostr Wallet Connect connections.

The API surface in the current handler contains these POST endpoints:

  • /api/nwc/mint
  • /api/nwc/revoke
  • /api/nwc/ledger/balance
  • /api/nwc/ledger/credit

Authentication is delegated to the standard Damage token flow. In practice, you first obtain an access_token from /accounts/auth/ and then send it as:

  • Authorization: Bearer <access_token>

The server also accepts the token as an access_token query parameter or via sessionid cookie, but the Bearer header is the cleanest option for scripts.

Authentication

Get an access token

curl -sS -X POST \
  https://run.dev.damagebdd.com/accounts/auth/ \
  -H 'content-type: application/json' \
  -d '{
        "username": "you@example.com",
        "password": "your-password"
      }'

Expected JSON response:

{
  "status": "ok",
  "access_token": "...",
  "address": "ak_..."
}

Store the token for later:

export DAMAGE_BASE='https://run.dev.damagebdd.com'
export DAMAGE_TOKEN='PASTE_ACCESS_TOKEN_HERE'

Python helper for authenticated requests

import requests

BASE = "https://run.dev.damagebdd.com"
TOKEN = "PASTE_ACCESS_TOKEN_HERE"

session = requests.Session()
session.headers.update({
    "Authorization": f"Bearer {TOKEN}",
    "content-type": "application/json",
    "accept": "application/json",
})


def post_json(path, payload):
    response = session.post(f"{BASE}{path}", json=payload, timeout=60)
    response.raise_for_status()
    if response.content:
        return response.json()
    return None

Endpoint: /api/nwc/mint

Creates a new NWC connection for the authenticated user. The handler generates a new client secret, derives the client pubkey, resolves the user ledger, and returns an NWC URI. If the user has no registered ledger yet, it may either set it up automatically or return setup intents, depending on execution mode and key availability.

Request body

  • relays: list of relay URLs. Optional.
  • max_single_sat: per-payment ceiling in sats. Optional. Default 10000.
  • max_total_sat: total ledger ceiling in sats. Optional. Default 100000.
  • expires_height: chain height expiry. Optional. Default 0.

curl example

curl -sS -X POST "$DAMAGE_BASE/api/nwc/mint" \
  -H "Authorization: Bearer $DAMAGE_TOKEN" \
  -H 'content-type: application/json' \
  -d '{
        "relays": ["wss://relay.damus.io"],
        "max_single_sat": 1000,
        "max_total_sat": 5000,
        "expires_height": 0
      }'

Python example

mint = post_json("/api/nwc/mint", {
    "relays": ["wss://relay.damus.io"],
    "max_single_sat": 1000,
    "max_total_sat": 5000,
    "expires_height": 0,
})

print(mint)
client_pubkey = mint["client_pubkey"]
nwc_uri = mint["nwc_uri"]

Typical success response

{
  "status": "ok",
  "owner": "ak_...",
  "ledger_ct": "ct_...",
  "ledger_mode": "server_signed",
  "client_pubkey": "<64-hex>",
  "secret_hex": "<32-byte-secret-hex>",
  "nwc_uri": "nostr+walletconnect://<wallet_pubkey>?relay=wss://...&secret=<secret_hex>",
  "wallet_pubkey": "<64-hex>",
  "relay": "wss://relay.damus.io",
  "intents": []
}

Notes

  • The current code returns HTTP 200 with a JSON body on success, not 204.
  • secret_hex is only returned at mint time, so persist it immediately if your client needs it.
  • In user_signed mode, intents contains a ledger registration intent instead of server-side execution.
  • In missing-ledger cases, the server may either auto-setup the ledger and still return status: "ok", or return status: "needs_ledger_setup" with setup intents.

Endpoint: /api/nwc/ledger/balance

Looks up the ledger-backed balance state for a given client_pubkey under the currently authenticated owner.

Request body

  • client_pubkey: required NWC client pubkey.

curl example

curl -sS -X POST "$DAMAGE_BASE/api/nwc/ledger/balance" \
  -H "Authorization: Bearer $DAMAGE_TOKEN" \
  -H 'content-type: application/json' \
  -d '{
        "client_pubkey": "YOUR_CLIENT_PUBKEY"
      }'

Python example

balance = post_json("/api/nwc/ledger/balance", {
    "client_pubkey": client_pubkey,
})
print(balance)

Response shape

{
  "status": "ok",
  "owner": "ak_...",
  "ledger_ct": "ct_...",
  "client_pubkey": "<64-hex>",
  "result": {
    "return_type": "ok",
    "return_value": "..."
  }
}

The exact shape of result.return_value depends on the smart-contract entrypoint serialization returned by ledger_call_user(..., "balance", ...).

Endpoint: /api/nwc/ledger/credit

Credits a ledger-backed NWC connection by a deterministic amount. This endpoint is explicitly admin-only in the handler.

Request body

  • client_pubkey: required NWC client pubkey.
  • amount_sat: amount to credit in sats.
  • ref: reference string.
  • meta: metadata string, commonly JSON encoded text.

curl example

curl -sS -X POST "$DAMAGE_BASE/api/nwc/ledger/credit" \
  -H "Authorization: Bearer $DAMAGE_TOKEN" \
  -H 'content-type: application/json' \
  -d '{
        "client_pubkey": "YOUR_CLIENT_PUBKEY",
        "amount_sat": 2000,
        "ref": "bdd-credit-1",
        "meta": "{}"
      }'

Python example

credit = post_json("/api/nwc/ledger/credit", {
    "client_pubkey": client_pubkey,
    "amount_sat": 2000,
    "ref": "bdd-credit-1",
    "meta": "{}",
})
print(credit)

Success response

{
  "status": "ok",
  "owner": "ak_...",
  "ledger_ct": "ct_...",
  "credited_sat": 2000,
  "intents": []
}

Notes

  • The handler converts amount_sat to millisatoshis before calling the ledger.
  • In user_signed mode, the response may include credit intents instead of the server executing the mutation.
  • The code expects role : = in request state before allowing this endpoint.

Endpoint: /api/nwc/revoke

Revokes a previously minted NWC connection identified by client_pubkey.

Request body

  • client_pubkey: required NWC client pubkey.

curl example

curl -sS -X POST "$DAMAGE_BASE/api/nwc/revoke" \
  -H "Authorization: Bearer $DAMAGE_TOKEN" \
  -H 'content-type: application/json' \
  -d '{
        "client_pubkey": "YOUR_CLIENT_PUBKEY"
      }'

Python example

revoke = post_json("/api/nwc/revoke", {
    "client_pubkey": client_pubkey,
})
print(revoke)

Success response

{
  "status": "ok",
  "revoked": true,
  "client_pubkey": "<64-hex>",
  "ledger_ct": "ct_...",
  "intents": []
}

In user_signed mode, intents may contain a revoke intent instead.

End-to-end example

curl workflow

export DAMAGE_BASE='https://run.dev.damagebdd.com'
export DAMAGE_TOKEN='PASTE_ACCESS_TOKEN_HERE'

MINT_JSON=$(curl -sS -X POST "$DAMAGE_BASE/api/nwc/mint" \
  -H "Authorization: Bearer $DAMAGE_TOKEN" \
  -H 'content-type: application/json' \
  -d '{
        "relays": ["wss://relay.damus.io"],
        "max_single_sat": 1000,
        "max_total_sat": 5000,
        "expires_height": 0
      }')

printf '%s\n' "$MINT_JSON"
CLIENT_PUBKEY=$(printf '%s' "$MINT_JSON" | jq -r '.client_pubkey')
NWC_URI=$(printf '%s' "$MINT_JSON" | jq -r '.nwc_uri')

curl -sS -X POST "$DAMAGE_BASE/api/nwc/ledger/balance" \
  -H "Authorization: Bearer $DAMAGE_TOKEN" \
  -H 'content-type: application/json' \
  -d "{\"client_pubkey\":\"$CLIENT_PUBKEY\"}"

curl -sS -X POST "$DAMAGE_BASE/api/nwc/ledger/credit" \
  -H "Authorization: Bearer $DAMAGE_TOKEN" \
  -H 'content-type: application/json' \
  -d "{\"client_pubkey\":\"$CLIENT_PUBKEY\",\"amount_sat\":2000,\"ref\":\"bdd-credit-1\",\"meta\":\"{}\"}"

curl -sS -X POST "$DAMAGE_BASE/api/nwc/revoke" \
  -H "Authorization: Bearer $DAMAGE_TOKEN" \
  -H 'content-type: application/json' \
  -d "{\"client_pubkey\":\"$CLIENT_PUBKEY\"}"

Python workflow

mint = post_json("/api/nwc/mint", {
    "relays": ["wss://relay.damus.io"],
    "max_single_sat": 1000,
    "max_total_sat": 5000,
    "expires_height": 0,
})

client_pubkey = mint["client_pubkey"]
print("NWC URI:", mint["nwc_uri"])

balance_1 = post_json("/api/nwc/ledger/balance", {
    "client_pubkey": client_pubkey,
})
print("Initial balance:", balance_1)

credit = post_json("/api/nwc/ledger/credit", {
    "client_pubkey": client_pubkey,
    "amount_sat": 2000,
    "ref": "bdd-credit-1",
    "meta": "{}",
})
print("Credit result:", credit)

balance_2 = post_json("/api/nwc/ledger/balance", {
    "client_pubkey": client_pubkey,
})
print("Balance after credit:", balance_2)

revoke = post_json("/api/nwc/revoke", {
    "client_pubkey": client_pubkey,
})
print("Revoke result:", revoke)

Error handling

Common error envelopes from this handler include:

{
  "status": "error",
  "error": "NO_LEDGER",
  "reason": "..."
}

Mint can also return a setup-oriented response:

{
  "status": "needs_ledger_setup",
  "reason": "...",
  "account_registry_ct": "ct_...",
  "client_pubkey": "<64-hex>",
  "secret_hex": "...",
  "nwc_uri": "nostr+walletconnect://...",
  "wallet_pubkey": "...",
  "relay": "wss://...",
  "intents": [
    {"type": "deploy_ledger", "...": "..."},
    {"type": "upsert_registry", "...": "..."},
    {"type": "ledger_register", "...": "..."}
  ]
}

For shell scripts, treat non-2xx HTTP codes as transport/application errors, and also inspect the JSON status field because some logical failures are expressed inside JSON payloads.

Practical caveats

  • Your BDD scenario currently expects 204 from /api/nwc/mint, but the handler shown here replies with 200 and a JSON body containing the minted values.
  • The sample scenario also uses Then I print the response immediately after checking for 204. That only makes sense if the endpoint actually returns a body; the current handler does.
  • /api/nwc/ledger/credit is admin-gated in the code, so a normal user token may not be sufficient unless the request state carries admin role.

Source basis

This manual was derived from the current damage_nwc_http handler, the shared Damage auth flow in damage_http, and the account auth endpoint in damage_accounts.