REST API Reference
The Lexe SDK sidecar exposes the following REST API endpoints:
GET /v2/health
PUT /v2/node/signup
PUT /v2/node/provision
GET /v2/node/node_info
GET /v2/node/analyze
POST /v2/node/pay
POST /v2/node/create_invoice
POST /v2/node/pay_invoice
POST /v2/node/create_offer
POST /v2/node/pay_offer
POST /v2/node/pay_lnurl
POST /v2/node/withdraw_lnurl
POST /v2/node/buy_with_cash_app
GET /v2/node/human_bitcoin_address
PUT /v2/node/human_bitcoin_address
PUT /v2/node/sync_payments
GET /v2/node/list_payments
POST /v2/node/clear_payments
GET /v2/node/payment
GET /v2/node/updated_payments
POST /v2/node/update_personal_note
GET /v2/node/list_channels
POST /v2/node/open_channel
POST /v2/node/close_channel
GET /v2/node/list_clients
POST /v2/node/create_client
PUT /v2/node/update_client
POST /v2/node/revoke_client
Conventions
Payments are uniquely identified by their index value string. Fetch the status
of a payment using the GET /v2/node/payment?index=<index> endpoint.
amount values are fixed-precision decimal values denominated in satoshis
(1 BTC = 100,000,000 satoshis) and serialized as strings. The representation
supports up-to millisatoshi precision (1 satoshi = 1,000 millisatoshis).
All timestamps indicate the number of milliseconds since the UNIX epoch.
Prefer longer request timeouts (e.g. 15 seconds) since your node may need time to startup and sync if it hasn't received any requests in a while.
GET /v2/health
Get the health status of the Lexe SDK sidecar. Returns HTTP 200 once the sidecar is running and ready to accept requests.
Response:
status:"ok"if configuration is OK, otherwise a warning or error message.
Examples:
PUT /v2/node/signup
Register with Lexe and provision your node. This must be done once before any other node endpoint will work. Signup is idempotent: calling it again for an account that already exists is a no-op.
Signup requires root seed credentials rather than client credentials. Provide the root seed via one of:
--root-seed/$LEXE_ROOT_SEED--root-seed-path/$LEXE_ROOT_SEED_PATH
and ensure the Authorization header is empty in the request;
if client credentials are supplied, the request is rejected.
Request:
The request body is optional. If provided, it should be a JSON object with the following fields:
partner_pk: String(optional): The hex-encodeduser_pkof a Lexe partner to associate with this account.
Response:
An empty object ({}) with HTTP 200 on success.
Examples:
# Sign up under a partner
$ curl -X PUT http://localhost:5393/v2/node/signup \
--header "content-type: application/json" \
--data '{ "partner_pk": "b484a4890b47358ee68684bcd502d2eefa1bc66cc0f8ac2e5f06384676be74eb" }'
{}
PUT /v2/node/provision
Provision your node to all recent trusted Lexe releases, ensuring it runs the most up-to-date enclave software. This fetches the set of enclaves to provision from Lexe, verifies they are trusted, then provisions any that are missing.
Provisioning is idempotent: if your node is already provisioned to all recent
releases, this is a no-op. It is good practice to call this after signup and
whenever you (re)start the sidecar, so your node picks up the latest release.
Like signup, provision currently requires root seed credentials (see the
signup endpoint above for how to provide them); delegated provisioning with
client credentials is not yet supported.
Request:
Empty.
Response:
An empty object ({}) with HTTP 200 on success.
Examples:
GET /v2/node/node_info
Fetch information about the node and wallet balance.
Request:
Empty.
Response:
version: The node's current semver version, e.g.0.6.9.measurement: The hex-encoded SGX 'measurement' of the current node. The measurement is the hash of the enclave binary.user_pk: The hex-encoded ed25519 user public key used to identify a Lexe user. The user keypair is derived from the root seed.-
node_pk: The hex-encoded secp256k1 Lightning node public key; thenode_id. -
balance: The sum of ourlightning_balanceand ouronchain_balance, in sats. -
lightning_balance: Total Lightning balance in sats, summed over all of our channels. lightning_sendable_balance: An estimated upper bound, in sats, on how much of our Lightning balance we can send to most recipients on the Lightning Network, accounting for Lightning limits such as our channel reserve, pending HTLCs, fees, etc. You should usually be able to spend this amount.-
lightning_max_sendable_balance: A hard upper bound on how much of our Lightning balance can be spent right now, in sats. This is always >=lightning_sendable_balance. Generally it is only possible to spend exactly this amount if the recipient is a Lexe user. -
onchain_balance: Total on-chain balance in sats, including unconfirmed funds. -
onchain_trusted_balance: Trusted on-chain balance in sats, including only confirmed funds and unconfirmed outputs originating from our own wallet. -
num_channels: The total number of Lightning channels. num_usable_channels: The number of channels which are currently usable, i.e.channel_readymessages have been exchanged and the channel peer is online. Is always less than or equal tonum_channels.
Examples:
$ curl http://localhost:5393/v2/node/node_info | jq .
{
"version": "0.9.2",
"measurement": "e9cc14a630c8c6973be2f7cfdfe1baac5d997e907485f4f21fd1ba179b0a0cb9",
"user_pk": "b484a4890b47358ee68684bcd502d2eefa1bc66cc0f8ac2e5f06384676be74eb",
"node_pk": "0203e73be064cc91d5e3c96d8e2f2f124f3196e07e9916b51307b6ff5419b59f6e",
"balance": "924823",
"lightning_balance": "461071",
"lightning_sendable_balance": "442478.190",
"lightning_max_sendable_balance": "445995.804",
"onchain_balance": "463752",
"onchain_trusted_balance": "463752",
"num_channels": 4,
"num_usable_channels": 4
}
GET /v2/node/analyze
Get information about a Bitcoin or Lightning payment string, including all
encoded payment and claim methods and their amount constraints. Returns
payables (outbound payment routes, sorted from most to least recommended) and
claimables (inbound claim routes, e.g. LNURL-withdraw).
For each entry, a callback URL is included with the payment string pre-filled
as a query parameter: payables point to POST /v2/node/pay and claimables
point to POST /v2/node/withdraw_lnurl. You can use this URL directly to pay or
claim without constructing the request body yourself.
The following encodings are supported:
- BIP 321 URI: bitcoin:bc1...
- Lightning URI: lightning:ln...
- BOLT 11 invoice: lnbc1...
- BOLT 12 offer: lno1...
- Onchain bitcoin address: bc1...
- Human Bitcoin Address: ₿username@lexe.app
- Lightning Address: username@lexe.app
- LNURL: lnurl1... or lnurlp://domain.com/path
Request:
Query parameters:
payment_string: String: The Bitcoin or Lightning payment string to analyze.
Response:
A JSON object with the following fields:
payables: A list of payable details, one per payment method encoded in the string, sorted from most to least recommended.
Each entry in payables contains:
- callback: String: A URL you can call (via POST /v2/node/pay) to pay this
specific payment method. If amount is null, you must specify an amount
before calling it - either by appending &amount=<amount> as a query
parameter or by including amount in the JSON body.
- kind: String: The payment method type. One of "invoice", "offer",
"onchain", or "lnurl-pay".
- Exactly one of invoice, offer, onchain, or lnurl will be present
(matching kind), containing the string encoding of that payment method.
- description: String | null: A description or memo from the recipient, if any.
- amount: String | null: The exact amount requested by the recipient, in sats.
null means the sender can choose the amount.
- min_amount: String | null: The minimum amount the recipient will accept, in sats.
- max_amount: String | null: The maximum amount the recipient will accept, in sats.
- expires_at: Int | null: Expiration timestamp, in milliseconds since the UNIX
epoch. null if the payable does not expire.
claimables: A list of claimable details, one per claim method encoded in the string.
Each entry in claimables contains:
- callback: String: A URL you can call (via POST /v2/node/withdraw_lnurl) to
claim via this specific method. If the amount is not fixed, you must specify an
amount before calling it - either by appending &amount=<amount> as a query
parameter or by including amount in the JSON body.
- kind: String: The claim method type. Currently always "lnurl-withdraw".
- lnurl: String: The LNURL-withdraw string encoding of the claim method.
- description: String | null: A default description from the service, if any.
- min_amount: String | null: The minimum amount that can be withdrawn, in sats.
- max_amount: String | null: The maximum amount that can be withdrawn, in sats.
Examples:
$ curl "http://localhost:5393/v2/node/analyze?payment_string=lnbc10u1p568eh0dqgf36kucmgpp5..." | jq .
{
"payables": [
{
"callback": "http://localhost:5393/v2/node/pay?payable=lnbc10u1p568eh0dqgf36kucmgpp5...",
"kind": "invoice",
"invoice": "lnbc10u1p568eh0dqgf36kucmgpp5...",
"description": "Coffee",
"amount": "1000",
"min_amount": null,
"max_amount": null,
"expires_at": 1772352767000
}
],
"claimables": []
}
# Amountless LNURL example
$ curl "http://localhost:5393/v2/node/analyze?payment_string=username%40lexe.app" | jq .
{
"payables": [
{
"callback": "http://localhost:5393/v2/node/pay?payable=username%40lexe.app",
"kind": "lnurl-pay",
"lnurl": "username@lexe.app",
"description": "Satoshi's Lightning Address",
"amount": null,
"min_amount": "1",
"max_amount": "1000000",
"expires_at": null
}
],
"claimables": []
}
# LNURL-withdraw example
$ curl "http://localhost:5393/v2/node/analyze?payment_string=lnurl1dp68gurn8ghj7mr0vdskc6r0wd6r5vesxqmrzvpsxqcz7ar9wd6xjum5d3kk7mt9wd6r5vesx..." | jq .
{
"payables": [],
"claimables": [
{
"callback": "http://localhost:5393/v2/node/withdraw_lnurl?lnurl=lnurl1dp68gurn8ghj7mr0vdskc6r0wd6r5vesxqmrzvpsxqcz7ar9wd6xjum5d3kk7mt9wd6r5vesx...",
"kind": "lnurl-withdraw",
"lnurl": "lnurl1dp68gurn8ghj7mr0vdskc6r0wd6r5vesxqmrzvpsxqcz7ar9wd6xjum5d3kk7mt9wd6r5vesx...",
"description": "Refund from merchant",
"min_amount": "1",
"max_amount": "50000"
}
]
}
POST /v2/node/pay
Pay any string which encodes a Bitcoin or Lightning payment method.
If multiple payment methods are encoded in the string, the best recommended
one is chosen. For finer control, use GET /v2/node/analyze first to see all
encoded payment methods and their callback URLs, then invoke a specific pay
endpoint (pay_invoice, pay_offer, etc.) for the method of your choice.
The following encodings are supported:
- BIP 321 URI: bitcoin:bc1...
- Lightning URI: lightning:ln...
- BOLT 11 invoice: lnbc1...
- BOLT 12 offer: lno1...
- Onchain bitcoin address: bc1...
- Human Bitcoin Address: ₿username@lexe.app
- Lightning Address: username@lexe.app
- LNURL: lnurl1... or lnurlp://domain.com/path
Request:
The payable and amount fields can be provided either as JSON body fields or
as query parameters (?payable=...&amount=...). If both are provided, they must
not conflict. This allows you to use the callback URLs returned by analyze
directly.
The request body should be a JSON object with the following fields:
payable: String(optional if provided as query param): The payment string to pay.amount: String(optional): The amount to pay in satoshis, as a string. Required when the payable has no encoded amount (e.g. amountless invoices, LNURL). If both the payable and the request specify an amount, they must match. For LNURL payables, the amount must be within the receiver's[min_amount, max_amount]range.message: String(optional): An optional message to the recipient. Supported for BOLT 12 and LNURL payments. Must be non-empty and ≤200 chars / ≤512 UTF-8 bytes if provided.personal_note: String(optional): A personal note to attach to the payment. The receiver will not see this note. If provided, must be non-empty and no longer than 200 chars / 512 UTF-8 bytes.
Response:
The full finalized Payment object (same fields as GET /v2/node/payment).
The call blocks until the payment reaches a terminal state ("completed" or
"failed"). The one exception is onchain sends, which return immediately with
the payment still in "pending" status, since on-chain confirmation takes
~1 hour.
Examples:
# Pay a BOLT 12 offer via callback URL returned by /analyze (query params)
$ curl -X POST "http://localhost:5393/v2/node/pay?payable=bitcoin%3A%3Famount%3D0.00000012%26lno%3Dlno1pqpzacq2gpyjqctdypsjqcn0d36..." \
--header "content-type: application/json" \
--data '{}' \
| jq .
{
"index": "0000001778115212041-fs_3ad7d5bf3e70d4299b474f9875dfbe4918b10c5fc688cab17ebda0e9883bfaab",
"rail": "offer",
"kind": "offer",
"direction": "outbound",
"hash": "a3f1c9e2d4b5687091a2b3c4d5e6f70819203a4b5c6d7e8f90a1b2c3d4e5f607",
"preimage": "f607e5d4c3b2a1908f7e6d5c4b3a20918007f6e5d4c3b2a1f09e8d7c6b5a4938",
"offer_id": "9b474f9875dfbe4918b10c5fc688cab17ebda0e9883bfaab3ad7d5bf3e70d429",
"txid": null,
"amount": "12",
"fees": "1",
"partner_pk": null,
"partner_prop_fee": null,
"partner_base_fee": null,
"status": "completed",
"status_msg": "completed",
"address": null,
"invoice": null,
"tx": null,
"payer_name": null,
"message": null,
"personal_note": null,
"priority": null,
"expires_at": null,
"finalized_at": 1778115213500,
"created_at": 1778115212041,
"updated_at": 1778115213500
}
# Pay a Lightning invoice via JSON body
$ curl -X POST http://localhost:5393/v2/node/pay \
--header "content-type: application/json" \
--data '{ "payable": "lnbc100n1p5lhsmnpp5ekfcd3yk03tvz9zeqndc044k40v039gu80rp6542fuuf8td5rxu..." }' \
| jq .
{
"index": "0000001778115215123-ln_e1f8e7fa3f3b43eb65afe4897ca1c63688636ba0a23b3011710e433b51bb3f9a",
"rail": "invoice",
"kind": "invoice",
"direction": "outbound",
"hash": "e1f8e7fa3f3b43eb65afe4897ca1c63688636ba0a23b3011710e433b51bb3f9a",
"preimage": "3f9a51bb3f9a23b3011710e433b51bb3688636ba0a23b3011710e1f8e7fae433",
"offer_id": null,
"txid": null,
"amount": "10000",
"fees": "1",
"partner_pk": null,
"partner_prop_fee": null,
"partner_base_fee": null,
"status": "completed",
"status_msg": "completed",
"address": null,
"invoice": "lnbc100n1p5lhsmnpp5ekfcd3yk03tvz9zeqndc044k40v039gu80rp6542fuuf8td5rxu...",
"tx": null,
"payer_name": null,
"message": null,
"personal_note": null,
"priority": null,
"expires_at": 1778115515123,
"finalized_at": 1778115216789,
"created_at": 1778115215123,
"updated_at": 1778115216789
}
# Pay a Lightning Address (amount required for LNURL)
$ curl -X POST http://localhost:5393/v2/node/pay \
--header "content-type: application/json" \
--data '{ "payable": "username@lexe.app", "amount": "1000", "message": "Coffee" }' \
| jq .
{
"index": "0000001778115218456-ln_e1f8e7fa3f3b43eb65afe4897ca1c63688636ba0a23b3011710e433b51bb3f9a",
"rail": "invoice",
"kind": "invoice",
"direction": "outbound",
"hash": "e1f8e7fa3f3b43eb65afe4897ca1c63688636ba0a23b3011710e433b51bb3f9a",
"preimage": "433b51bb3f9a23b3011710e1f8e7fae3688636ba0a23b3011710e433b51bb3f9a",
"offer_id": null,
"txid": null,
"amount": "1000",
"fees": "1",
"partner_pk": null,
"partner_prop_fee": null,
"partner_base_fee": null,
"status": "completed",
"status_msg": "completed",
"address": null,
"invoice": "lnbc10u1p5lhsmnpp5ekfcd3yk03tvz9zeqndc044k40v039gu80rp6542fuuf8td5rxu...",
"tx": null,
"payer_name": null,
"message": "Coffee",
"personal_note": null,
"priority": null,
"expires_at": 1778115518456,
"finalized_at": 1778115219999,
"created_at": 1778115218456,
"updated_at": 1778115219999
}
POST /v2/node/create_invoice
Create a new BOLT 11 Lightning invoice to receive Bitcoin over the Lightning network.
Request:
The request body should be a JSON object with the following fields:
expiration_secs: Int(optional): The number of seconds until the invoice expires. If not specified, defaults to 86400 (1 day).amount: String(optional): The amount to request in satoshis, as a string. If not specified, the payer will decide the amount.description: String(optional): The payment description that will be presented to the payer.personal_note: String(optional): A personal note to attach to the invoice. The payer will not see this note. If provided, must be non-empty and no longer than 200 chars / 512 UTF-8 bytes.partner_pk: String(optional): The hex-encoded user_pk of a Lexe partner setting the fee for this payment instead of using Lexe's default fees. Must be set forpartner_prop_feeandpartner_base_feeto take effect.partner_prop_fee: Int(optional): The partner-chosen proportional fee in parts per million (ppm). Required ifpartner_pkis set. Minimum: 5000 ppm. Maximum: 500000 ppm (50%).partner_base_fee: String(optional): The partner-chosen base fee in satoshis, as a string. If set,amountmust also be set.
Response:
The response includes the encoded invoice string, which should be presented to
the payer to complete the payment.
The index is a unique identifier for the invoice, which can be used to track
the payment status via GET /v2/node/payment.
index: Identifier for this inbound invoice payment.invoice: The string-encoded BOLT 11 invoice.description: The description encoded in the invoice, if one was provided.amount: The amount encoded in the invoice, if there was one. Returningnullmeans we created an amountless invoice.created_at: The invoice creation time, in milliseconds since the UNIX epoch.expires_at: The invoice expiration time, in milliseconds since the UNIX epoch.payment_hash: The hex-encoded payment hash of the invoice.payment_secret: The payment secret of the invoice.
Examples:
$ curl -X POST http://localhost:5393/v2/node/create_invoice \
--header "content-type: application/json" \
--data '{ "expiration_secs": 3600 }' \
| jq .
{
"index": "0000001772349163844-ln_003dd23aec576e7d0d85aa991ff9c2dc471fd7c863ff31b93bfbb02836eb56b5",
"invoice": "lnbc1p568ehtdqqpp5qq7aywhv2ah86rv942v3l7wzm3r3l47gv0lnrwfmlwczsdht266scqpcsp5a3v0skathghudyaszdze77dnuh7pnza7phagq5c7ke5mqra3nuqs9qyysgqxqrrssnp4qgp7wwlqvnxfr40re9kcute0zf8nr9hq06v3ddgnq7m074qekk0kurzjqv22wafr68wtchd4vzq7mj7zf2uzpv67xsaxcemfzak7wp7p0r29wzxnguqq2qsqqcqqqqqqqqqqhwqqfq73dfkaqrghzc0lpgeandl5zfjxh2z6fhk47sfph40dqv72tefmw9j4a7c8w0f0l7uyjfa9dzwpy7ypllmvmxd4n2ggfufd593yh5v7cq5uaa9s",
"description": null,
"amount": null,
"created_at": 1772349163000,
"expires_at": 1772352763000,
"payment_hash": "003dd23aec576e7d0d85aa991ff9c2dc471fd7c863ff31b93bfbb02836eb56b5",
"payment_secret": "ec58f85babba2fc693b013459f79b3e5fc198bbe0dfa80531eb669b00fb19f01"
}
$ curl -X POST http://localhost:5393/v2/node/create_invoice \
--header "content-type: application/json" \
--data '{ "expiration_secs": 3600, "amount": "1000", "description": "Lunch" }' \
| jq .
{
"index": "0000001772349167284-ln_16ec06bf32459f0bd3fbbf2fdaace70b60dcadbbfb170de097edd5e515d7ac18",
"invoice": "lnbc10u1p568eh0dqgf36kucmgpp5zmkqd0ejgk0sh5lmhuha4t88pdsdetdmlvtsmcyhah2729wh4svqcqpcsp5red8hqrl6wx8zvufsgfwge2chcrgd9cmxeu8kdl5u5egf2wqhjvq9qyysgqxqrrssnp4qgp7wwlqvnxfr40re9kcute0zf8nr9hq06v3ddgnq7m074qekk0kurzjqv22wafr68wtchd4vzq7mj7zf2uzpv67xsaxcemfzak7wp7p0r29wzxnguqq2qsqqcqqqqqqqqqqhwqqfqd9lunvz89ed636ymga55aypfgdx9g0fgxga4edcxatwzzac2ssqpt6gyuy73ezyav7gsg2tvj92cg9wvzlrrh7jhc76he6r8pllq0qqquygrtn",
"description": "Lunch",
"amount": "1000",
"created_at": 1772349167000,
"expires_at": 1772352767000,
"payment_hash": "16ec06bf32459f0bd3fbbf2fdaace70b60dcadbbfb170de097edd5e515d7ac18",
"payment_secret": "1e5a7b807fd38c7133898212e46558be0686971b36787b37f4e53284a9c0bc98"
}
POST /v2/node/pay_invoice
Pay a BOLT 11 Lightning invoice.
Request:
The request body should be a JSON object with the following fields:
invoice: String: The encoded invoice string to pay.fallback_amount: String(optional): For invoices without an amount specified, you must specify a fallback amount to pay.personal_note: String(optional): A personal note to attach to the payment. The receiver will not see this note. If provided, must be non-empty and no longer than 200 chars / 512 UTF-8 bytes.
Response:
The full finalized Payment object (same fields as GET /v2/node/payment).
The call blocks until the payment reaches a terminal state ("completed" or
"failed").
Examples:
$ curl -X POST http://localhost:5393/v2/node/pay_invoice \
--header "content-type: application/json" \
--data '{ "invoice": "lnbc100n1p5qz7z2dq58skjqnr90pjjq4r9wd6qpp5u8uw073l8dp7ked0ujyhegwxx6yxx6aq5ganqyt3pepnk5dm87dqcqpcsp5nrs44f3upgxysnylrrpyrxs96mgazjjstuykyew74zv0najzkdeq9qyysgqxqyz5vqnp4q0w73a6xytxxrhuuvqnqjckemyhv6avveuftl64zzm5878vq3zr4jrzjqv22wafr68wtchd4vzq7mj7zf2uzpv67xsaxcemfzak7wp7p0r29wz5ecsqq2pgqqcqqqqqqqqqqhwqqfqrpeeq5xdys8vcfcark45w992h6j5nhajc62wet0q25ggxjwhtcfn8c3qx30fqzq8mqxfdtks57zw25zp0z2kl9yrfwkkthxclawxpfcqtdcpfu" }' \
| jq .
{
"index": "0000001744926842458-ln_e1f8e7fa3f3b43eb65afe4897ca1c63688636ba0a23b3011710e433b51bb3f9a",
"rail": "invoice",
"kind": "invoice",
"direction": "outbound",
"hash": "e1f8e7fa3f3b43eb65afe4897ca1c63688636ba0a23b3011710e433b51bb3f9a",
"preimage": "3011710e433b51bb3f9a23b3011710e1f8e7fae3688636ba0a23b3011710e433b",
"offer_id": null,
"txid": null,
"amount": "10000",
"fees": "1",
"partner_pk": null,
"partner_prop_fee": null,
"partner_base_fee": null,
"status": "completed",
"status_msg": "completed",
"address": null,
"invoice": "lnbc100n1p5qz7z2dq58skjqnr90pjjq4r9wd6qpp5u8uw073l8dp7ked0ujyhegwxx6yxx6aq5ganqyt3pepnk5dm87dqcqpc...",
"tx": null,
"payer_name": null,
"message": null,
"personal_note": null,
"priority": null,
"expires_at": 1744927142458,
"finalized_at": 1744926843901,
"created_at": 1744926842458,
"updated_at": 1744926843901
}
POST /v2/node/create_offer
Create a reusable BOLT 12 offer to receive Bitcoin over the Lightning network. Unlike invoices, offers are reusable: multiple payments can be made to it, including from multiple payers.
Request:
The request body should be a JSON object with the following fields:
description: String(optional): A description that will be presented to the payer. If provided, must be non-empty and no longer than 200 chars / 512 UTF-8 bytes.min_amount: String(optional): The minimum payment amount as a decimal string in satoshis. If not specified, the payer can send any amount.expiration_secs: Int(optional): The number of seconds until the offer expires. If not specified, the offer does not expire.
Response:
offer: The string-encoded BOLT 12 offer.
Examples:
$ curl -X POST http://localhost:5393/v2/node/create_offer \
--header "content-type: application/json" \
--data '{}' \
| jq .
{
"offer": "lno1qgsqvgnwgcg35z6ee2h3yczraddm72xrfua9uve2rlrm9deu7xyfzrcgqyqs..."
}
$ curl -X POST http://localhost:5393/v2/node/create_offer \
--header "content-type: application/json" \
--data '{ "description": "Tips", "min_amount": "100" }' \
| jq .
{
"offer": "lno1qgsqvgnwgcg35z6ee2h3yczraddm72xrfua9uve2rlrm9deu7xyfzrcgqyqs..."
}
POST /v2/node/pay_offer
Pay a BOLT 12 offer.
Request:
The request body should be a JSON object with the following fields:
offer: String: The encoded BOLT 12 offer string to pay.amount: String: The amount to pay in satoshis. If the offer specifies a minimum amount, this value must satisfy that minimum.message: String(optional): A message included in the BOLT 12 invoice request and visible to the recipient. If provided, must be non-empty and no longer than 200 chars / 512 UTF-8 bytes.personal_note: String(optional): A personal note to attach to the payment. The receiver will not see this note. If provided, must be non-empty and no longer than 200 chars / 512 UTF-8 bytes.
Response:
The full finalized Payment object (same fields as GET /v2/node/payment).
The call blocks until the payment reaches a terminal state ("completed" or
"failed").
Examples:
$ curl -X POST http://localhost:5393/v2/node/pay_offer \
--header "content-type: application/json" \
--data '{ "offer": "lno1qgsqvgnwgcg35z6ee2h3yczraddm72xrfua9uve2rlrm9deu7xyfzrcgqyqs...", "amount": "1000" }' \
| jq .
{
"index": "0000001777078225203-fs_b7f108c910b574cd8b69330881db1d4b9df73730eca49f92bfd167784715af29",
"rail": "offer",
"kind": "offer",
"direction": "outbound",
"hash": "b7f108c910b574cd8b69330881db1d4b9df73730eca49f92bfd167784715af29",
"preimage": "4715af29b7f108c910b574cd8b69330881db1d4b9df73730eca49f92bfd16778",
"offer_id": "0eca49f92bfd167784715af29b7f108c910b574cd8b69330881db1d4b9df7373",
"txid": null,
"amount": "1000",
"fees": "1",
"partner_pk": null,
"partner_prop_fee": null,
"partner_base_fee": null,
"status": "completed",
"status_msg": "completed",
"address": null,
"invoice": null,
"tx": null,
"payer_name": null,
"message": null,
"personal_note": null,
"priority": null,
"expires_at": null,
"finalized_at": 1777078226654,
"created_at": 1777078225203,
"updated_at": 1777078226654
}
POST /v2/node/pay_lnurl
Pay an LNURL-pay endpoint or Lightning Address.
Request:
The request body should be a JSON object with the following fields:
lnurl: String: The LNURL (lnurl1...orlnurlp://...) or Lightning Address (username@lexe.app) to pay.amount: String: The amount to pay in satoshis. Must be within the receiver's[min_amount, max_amount]range, which can be determined using theanalyzeendpoint.message: String(optional): A message to the recipient, visible to them. Only sent if the endpoint supports comments (LUD-12), and truncated to the endpoint's comment length limit if needed.personal_note: String(optional): A personal note to attach to the payment. The receiver will not see this note. If provided, must be non-empty and no longer than 200 chars / 512 UTF-8 bytes.
Response:
The full finalized Payment object (same fields as GET /v2/node/payment).
The call blocks until the payment reaches a terminal state ("completed" or
"failed").
Examples:
$ curl -X POST http://localhost:5393/v2/node/pay_lnurl \
--header "content-type: application/json" \
--data '{ "lnurl": "username@lexe.app", "amount": "1000", "message": "Coffee" }' \
| jq .
{
"index": "0000001780626916507-ln_115bec14cbcd3a0d8aa08409c7fdbd156e810e71dc487ed4798e85f81d6a4f5f",
"rail": "invoice",
"kind": "invoice",
"direction": "outbound",
"hash": "115bec14cbcd3a0d8aa08409c7fdbd156e810e71dc487ed4798e85f81d6a4f5f",
"preimage": "f808fdceb967229379150b06a13b674cb8f577857f84025e4dbce27ece81d9f3",
"offer_id": null,
"txid": null,
"amount": "10",
"fees": "0.051",
"partner_pk": null,
"partner_prop_fee": null,
"partner_base_fee": null,
"status": "completed",
"status_msg": "completed",
"address": null,
"invoice": "lnbc100n1p4zydwapp5z9d7c9xte5aqmz4qssyu0ldaz4hgzrn3m3y8a4re36zls8t2fa0shp55ftsvdk...",
"tx": null,
"payer_name": null,
"message": "Coffee",
"personal_note": null,
"priority": null,
"expires_at": 1780713309000,
"finalized_at": 1780626918000,
"created_at": 1780626916507,
"updated_at": 1780626918000
}
POST /v2/node/withdraw_lnurl
Withdraw funds from an LNURL-withdraw endpoint.
Request:
The lnurl and amount fields can be provided either as JSON body fields or as
query parameters (?lnurl=...&amount=...). If both are provided, they must not
conflict. This allows you to use the callback URLs returned by analyze
directly.
The request body should be a JSON object with the following fields:
lnurl: String(optional if provided as query param): The LNURL (lnurl1...orlnurlw://...) to withdraw from.amount: String(optional): The amount to withdraw in satoshis. Must be within the endpoint's[min_amount, max_amount]range, which can be determined using theanalyzeendpoint. If not specified, the maximum allowed amount is withdrawn.description: String(optional): A description to encode into the withdrawal invoice, visible to the LNURL endpoint. If not specified, the endpoint's own default description is used.personal_note: String(optional): A personal note to attach to the withdrawal. The LNURL endpoint will not see this note. If provided, must be non-empty and no longer than 200 chars / 512 UTF-8 bytes.
Response:
The full finalized Payment object (same fields as GET /v2/node/payment).
The call blocks until the payment reaches a terminal state ("completed" or
"failed").
Examples:
$ curl -X POST http://localhost:5393/v2/node/withdraw_lnurl \
--header "content-type: application/json" \
--data '{ "lnurl": "lnurl1dp68gurn8ghj7mr0vdskc6r0wd6r5vesxqmrzvpsxqcz7ar9wd6xjum5d3kk7mt9wd6r5vesx...", "amount": "1000" }' \
| jq .
{
"index": "0000001780625111393-ln_dada0fc889358d168168ca2dc2610fc8e9df345b0f50402033998749b057b9ed",
"rail": "invoice",
"kind": "invoice",
"direction": "inbound",
"hash": "dada0fc889358d168168ca2dc2610fc8e9df345b0f50402033998749b057b9ed",
"preimage": "6881c332c70f676181d8bd9ed380b029786b18f7c82a85b3a4248114f9991d13",
"offer_id": null,
"txid": null,
"amount": "9.95",
"fees": "0.05",
"partner_pk": null,
"partner_prop_fee": null,
"partner_base_fee": null,
"status": "completed",
"status_msg": "completed",
"address": null,
"invoice": "lnbc100n1p4zytkhdq8w3jhxaqpp5mtdqljyfxkx3dqtgegkuycg0er5a7dzmpagyqgpnnxr5nvzhh8ks...",
"tx": null,
"payer_name": null,
"message": null,
"personal_note": null,
"priority": null,
"expires_at": 1780711511000,
"finalized_at": 1780625120867,
"created_at": 1780625111393,
"updated_at": 1780625120867
}
POST /v2/node/buy_with_cash_app
Buy Bitcoin with Cash App.
Given an amount of Bitcoin that the user wants to buy, returns a Cash App URL that you can redirect your user to complete the purchase. Cash App buys are instant and land directly into Lexe wallet.
For the smoothest user experience, you should encourage your user to open this URL on a device where Cash App is already set up.
Request:
The request body should be a JSON object with the following fields:
amount: String: The amount of Bitcoin to buy in satoshis. Must be at least 5000 sats.
Response:
A JSON object with the following fields:
redirect_url: String: Redirect your user to this URL to complete the purchase; for the smoothest experience, have them open it on a device where Cash App is already set up. The bought Bitcoin lands directly into Lexe wallet.index: String: Identifier for the inbound payment funding this buy. Use it to track the payment status viaGET /v2/node/paymentonce Cash App has funded it.
Examples:
$ curl -X POST http://localhost:5393/v2/node/buy_with_cash_app \
--header "content-type: application/json" \
--data '{ "amount": "5000" }' \
| jq .
{
"redirect_url": "https://cash.app/launch/lightning/lnbc50u1p4zytkhdq8w3jhxaqpp5mtdqljyfxkx3dqtgegkuycg0er5a7dzmpagyqgpnnxr5nvzhh8ks...",
"index": "0000001780626916507-ln_115bec14cbcd3a0d8aa08409c7fdbd156e810e71dc487ed4798e85f81d6a4f5f"
}
GET /v2/node/human_bitcoin_address
Get the user's Human Bitcoin Address.
The Human Bitcoin Address (BIP 353), e.g. ₿username@lexe.app, is a
human-readable address which others can pay to send Bitcoin to this wallet.
It also works as a Lightning Address (username@lexe.app) for senders which
support LNURL but not BIP 353.
Request:
(No request parameters)
Response:
A JSON object with the following fields:
human_bitcoin_address: String: The Human Bitcoin Address (BIP 353), e.g.₿username@lexe.app.lightning_address: String: The Lightning Address, e.g.username@lexe.app.offer: String: The string-encoded BOLT 12 offer that the Human Bitcoin Address resolves to.updatable: Bool: Whether the username can currently be changed. Usernames are updatable for 24 hours after being claimed, then frozen for 90 days.
Examples:
$ curl http://localhost:5393/v2/node/human_bitcoin_address | jq .
{
"human_bitcoin_address": "₿username@lexe.app",
"lightning_address": "username@lexe.app",
"offer": "lno1qgsqvgnwgcg35z6ee2h3yczraddm72xrfua9uve2rlrm9deu7xyfzrcgqyqs...",
"updatable": true
}
PUT /v2/node/human_bitcoin_address
Claim or update the user's custom Human Bitcoin Address.
Sets this wallet's Human Bitcoin Address to ₿{username}@lexe.app and its
Lightning Address to {username}@lexe.app. Usernames must be 6 to 24
characters of lowercase alphanumerics and hyphens, and must not start with,
end with, or contain consecutive hyphens.
Claiming requires a total wallet balance of at least 10000 sats. Once claimed, the username can be changed for 24 hours, then is frozen for 90 days.
Request:
A JSON object with the following fields:
username: String: The username to claim, e.g."username"for₿username@lexe.app.
Response:
A JSON object with the following fields:
human_bitcoin_address: String: The Human Bitcoin Address (BIP 353), e.g.₿username@lexe.app.lightning_address: String: The Lightning Address, e.g.username@lexe.app.offer: String: The string-encoded BOLT 12 offer that the Human Bitcoin Address resolves to.updatable: Bool: Whether the username can currently be changed. Usernames are updatable for 24 hours after being claimed, then frozen for 90 days.
Examples:
$ curl -X PUT http://localhost:5393/v2/node/human_bitcoin_address \
--header "content-type: application/json" \
--data '{ "username": "username" }' \
| jq .
{
"human_bitcoin_address": "₿username@lexe.app",
"lightning_address": "username@lexe.app",
"offer": "lno1qgsqvgnwgcg35z6ee2h3yczraddm72xrfua9uve2rlrm9deu7xyfzrcgqyqs...",
"updatable": true
}
PUT /v2/node/sync_payments
Sync the sidecar's local payment cache with the latest payment data from your node. Fetches all payments that are new or have been updated since the last sync and persists them locally, then returns a summary of what changed.
Request:
Empty.
Response:
num_new: The number of new payments added to the local cache.num_updated: The number of existing payments that were updated.
Examples:
GET /v2/node/list_payments
List payments from the sidecar's local cache, filtered by status and returned
in the requested order with cursor-based pagination. Call sync_payments first
to ensure the cache reflects the latest data from your node.
Request:
Query parameters:
filter: String(required): Which payments to include. One of"all","pending","completed","failed", or"finalized"(completed or failed).order: String(optional): Sort order, either"desc"(newest first; the default) or"asc"(oldest first).limit: Int(optional): The maximum number of payments to return. Defaults to 100.after: String(optional): Pagination cursor. Pass thenext_indexfrom the previous response to fetch the next page. The cursor is exclusive: the payment with this index is not included in the results.
Response:
payments: A list of payments (same fields asGET /v2/node/payment), in the requested order.next_index: The pagination cursor for the next page, ornullif there are no more results. Pass this asafteron the next call.
Examples:
# List the 2 most recent completed payments
$ curl "http://localhost:5393/v2/node/list_payments?filter=completed&limit=2" | jq .
{
"payments": [
{
"index": "0000001772349163900-ln_16ec06bf32459f0bd3fbbf2fdaace70b60dcadbbfb170de097edd5e515d7ac18",
"rail": "invoice",
"kind": "invoice",
"direction": "outbound",
"status": "completed",
"amount": "5000",
"fees": "1",
"created_at": 1772349163900,
"updated_at": 1772349205456
// ... remaining payment fields ...
},
{
"index": "0000001772349163844-ln_003dd23aec576e7d0d85aa991ff9c2dc471fd7c863ff31b93bfbb02836eb56b5",
"rail": "invoice",
"kind": "invoice",
"direction": "inbound",
"status": "completed",
"amount": "1000",
"fees": "0",
"created_at": 1772349163844,
"updated_at": 1772349200123
// ... remaining payment fields ...
}
],
"next_index": "0000001772349163844-ln_003dd23aec576e7d0d85aa991ff9c2dc471fd7c863ff31b93bfbb02836eb56b5"
}
# Continue paginating: pass the response's next_index as after
$ curl "http://localhost:5393/v2/node/list_payments?filter=completed&limit=2&after=0000001772349163844-ln_003dd23aec576e7d0d85aa991ff9c2dc471fd7c863ff31b93bfbb02836eb56b5" \
| jq .
POST /v2/node/clear_payments
Clear all payment data from the sidecar's local cache. Remote data on your node
is not affected; call sync_payments to re-populate the cache.
Request:
Empty.
Response:
An empty object ({}) with HTTP 200 on success.
Examples:
GET /v2/node/payment
Use this endpoint to query the status of a payment or invoice. Payments will transition
through the following status states: "pending" -> "completed" or "pending" -> "failed".
Once a payment is finalized (either completed or failed), you do not need to query
the payment any more.
Request:
The request should include the index of the payment query as a query string
parameter.
Response:
The response includes the payment details. If the payment is not found, the endpoint returns HTTP 404.
index: Unique payment identifier, ordered bycreated_at.rail: The technical payment mechanism:"onchain","invoice","offer","spontaneous","waived_fee", etc.kind: The application-level payment kind, e.g."onchain","invoice","offer","spontaneous","waived_channel_fee","waived_liquidity_fee".direction: The payment direction:"inbound","outbound", or"info".hash: (Lightning payments only) Hex-encoded payment hash (64 chars).preimage: (Lightning payments only) Hex-encoded payment preimage (64 chars). Serves as proof-of-payment for outbound payments. For inbound payments, only populated if the payment succeeded.offer_id: (Offer payments only) Hex-encoded BOLT12 offer id (64 chars).txid: (Onchain payments only) The hex-encoded Bitcoin txid.amount: The payment amount in satoshis, ornullfor pending amountless invoices.fees: Fees paid in satoshis.partner_pk: (optional) Hex-encoded partner user public key, if a Lexe partner set the fees for this payment instead of using Lexe's default fees.partner_prop_fee: (optional) The proportional fee set by the partner, in parts per million.partner_base_fee: (optional) The base fee set by the partner, in satoshis.status: The status of this payment:"pending","completed","failed".status_msg: The payment status as a human-readable message. These strings are customized per payment type, e.g. "invoice generated", "timed out".address: (Onchain send only) The destination Bitcoin address.invoice: (Invoice payments only) The BOLT 11 invoice string.tx: (Onchain payments only) The raw Bitcoin transaction.payer_name: (Offer payments only) The payer's self-reported name.message: (Offer payments, LNURL-pay invoices) A payer-provided message.personal_note: An optional personal note attached to this payment.priority: (Onchain send only) The confirmation priority:"high","normal","background".expires_at: The invoice or offer expiry time, in milliseconds since the UNIX epoch.finalized_at: If this payment is finalized, meaning it is "completed" or "failed", this is the time it was finalized, in milliseconds since the UNIX epoch.created_at: When this payment was created, in milliseconds since the UNIX epoch.updated_at: When this payment was last updated, in milliseconds since the UNIX epoch.
Examples:
$ curl 'http://localhost:5393/v2/node/payment?index=0000001772349163844-ln_003dd23aec576e7d0d85aa991ff9c2dc471fd7c863ff31b93bfbb02836eb56b5' \
| jq .
{
"index": "0000001772349163844-ln_003dd23aec576e7d0d85aa991ff9c2dc471fd7c863ff31b93bfbb02836eb56b5",
"rail": "invoice",
"kind": "invoice",
"direction": "inbound",
"hash": null,
"preimage": null,
"offer_id": null,
"txid": null,
"amount": null,
"fees": "0",
"partner_pk": null,
"partner_prop_fee": null,
"partner_base_fee": null,
"status": "pending",
"status_msg": "invoice generated",
"address": null,
"invoice": "lnbc1p568ehtdqqpp5qq7aywhv2ah86rv942v3l7wzm3r3l47gv0lnrwfmlwczsdht266scqpcsp5a3v0skathghudyaszdze77dnuh7pnza7phagq5c7ke5mqra3nuqs9qyysgqxqrrssnp4qgp7wwlqvnxfr40re9kcute0zf8nr9hq06v3ddgnq7m074qekk0kurzjqv22wafr68wtchd4vzq7mj7zf2uzpv67xsaxcemfzak7wp7p0r29wzxnguqq2qsqqcqqqqqqqqqqhwqqfq73dfkaqrghzc0lpgeandl5zfjxh2z6fhk47sfph40dqv72tefmw9j4a7c8w0f0l7uyjfa9dzwpy7ypllmvmxd4n2ggfufd593yh5v7cq5uaa9s",
"tx": null,
"payer_name": null,
"message": null,
"personal_note": null,
"priority": null,
"expires_at": 1772352763000,
"finalized_at": null,
"created_at": 1772349163844,
"updated_at": 1772349163844
}
# Example of missing payment (returns HTTP 404)
$ curl -i 'http://localhost:5393/v2/node/payment?index=0000000000000000000-ln_0000000000000000000000000000000000000000000000000000000000000000'
HTTP/1.1 404 Not Found
content-type: application/json
content-length: 68
date: Thu, 11 Sep 2025 22:26:53 GMT
{
"code": 107,
"msg": "Payment not found",
"data": null,
"sensitive": false
}
GET /v2/node/updated_payments
Fetch a batch of payments in ascending updated_at order, starting from a
given updated_at index. Use this to incrementally poll for payment updates:
pass the updated_index cursor returned by the previous call as
start_index, and you'll receive only payments updated since.
Request:
Query parameters:
start_index(optional): The cursor at which the results should start, exclusive. If given, payments that were last updated earlier than or equal to this will not be returned. If omitted, the least recently updated payments will be returned first.limit(optional): Maximum number of payments to return. Max 100, defaults to 50.
Response:
A JSON object with the following fields:
payments: A list of payments in ascendingupdated_atorder, with the same fields asGET /v2/node/payment.updated_index: Theupdated_atindex of the last payment in the returned batch, ornullif the batch is empty. Pass this asstart_indexon the next call to continue paginating.
Examples:
# Fetch the first page of updated payments
$ curl 'http://localhost:5393/v2/node/updated_payments?limit=2' | jq .
{
"payments": [
{
"index": "0000001772349163844-ln_003dd23aec576e7d0d85aa991ff9c2dc471fd7c863ff31b93bfbb02836eb56b5",
"rail": "invoice",
"kind": "invoice",
"direction": "inbound",
"status": "completed",
"amount": "1000",
"fees": "0",
"created_at": 1772349163844,
"updated_at": 1772349200123
// ... remaining payment fields ...
},
{
"index": "0000001772349163900-ln_16ec06bf32459f0bd3fbbf2fdaace70b60dcadbbfb170de097edd5e515d7ac18",
"rail": "invoice",
"kind": "invoice",
"direction": "outbound",
"status": "completed",
"amount": "5000",
"fees": "1",
"created_at": 1772349163900,
"updated_at": 1772349205456
// ... remaining payment fields ...
}
],
"updated_index": "0000001772349205456-ln_16ec06bf32459f0bd3fbbf2fdaace70b60dcadbbfb170de097edd5e515d7ac18"
}
# Continue paginating: pass the response's updated_index as start_index
$ curl 'http://localhost:5393/v2/node/updated_payments?start_index=0000001772349205456-ln_16ec06bf32459f0bd3fbbf2fdaace70b60dcadbbfb170de097edd5e515d7ac18&limit=2' \
| jq .
POST /v2/node/update_personal_note
Update the personal note attached to an existing payment. The note is stored on your node and is never visible to the counterparty.
Request:
The request body should be a JSON object with the following fields:
index: String: Theindexof the payment to update.note: String(optional): The updated note. Omit or set tonullto clear the note. If provided, must be non-empty and no longer than 200 chars / 512 UTF-8 bytes. (The legacy field namepersonal_noteis also accepted.)
Response:
An empty object ({}) with HTTP 200 on success.
Examples:
# Set or update the note
$ curl -X POST http://localhost:5393/v2/node/update_personal_note \
--header "content-type: application/json" \
--data '{ "index": "0000001772349163844-ln_003dd23aec576e7d0d85aa991ff9c2dc471fd7c863ff31b93bfbb02836eb56b5", "note": "Dinner with Alice" }'
{}
# Clear the note
$ curl -X POST http://localhost:5393/v2/node/update_personal_note \
--header "content-type: application/json" \
--data '{ "index": "0000001772349163844-ln_003dd23aec576e7d0d85aa991ff9c2dc471fd7c863ff31b93bfbb02836eb56b5", "note": null }'
{}
GET /v2/node/list_channels
List this node's Lightning channels. The counterparty is always the Lexe LSP.
Request:
Empty.
Response:
channels: A list of channel details, one per channel. Each entry contains:channel_id: String: The hex-encoded id of the channel.user_channel_id: String: The hex-encoded user-provided id associated with the channel throughout its lifetime.funding_txo: String | null: The channel's funding transaction output as<txid>_<index>, ornullif the funding transaction is not yet confirmed.is_usable: Bool: Whether the channel is ready and its counterparty online, so it can send and receive payments right now.channel_value: String: The total value of the channel, in sats.our_balance: String: Our balance in the channel, in sats.their_balance: String: The counterparty's balance in the channel, in sats.punishment_reserve: String: The portion of our balance we must keep in reserve as anti-cheating collateral, in sats. This is unspendable and does not count towardsoutbound_capacity.outbound_capacity: String: How much of our balance is currently available to send, in sats.inbound_capacity: String: How much of the counterparty's balance is available for us to receive, in sats.
Examples:
$ curl http://localhost:5393/v2/node/list_channels | jq .
{
"channels": [
{
"channel_id": "a1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f90",
"user_channel_id": "0123456789abcdef0123456789abcdef",
"funding_txo": "9f8e7d6c5b4a39281706f5e4d3c2b1a09f8e7d6c5b4a39281706f5e4d3c2b1a0_0",
"is_usable": true,
"channel_value": "500000",
"our_balance": "200000",
"their_balance": "300000",
"punishment_reserve": "5000",
"outbound_capacity": "195000",
"inbound_capacity": "295000"
}
]
}
POST /v2/node/open_channel
Open a Lightning channel from this node to Lexe's LSP.
Request:
The request body should be a JSON object with the following fields:
value: String: The value of the channel to open, in sats.user_channel_id: String(optional): A user-provided id associated with the channel throughout its lifetime. Also used for idempotency: retrying with the sameuser_channel_idwon't accidentally open another channel. If omitted, a random id is generated, which provides no idempotency across calls.
Response:
channel_id: String: The hex-encoded id of the newly opened channel.user_channel_id: String: The hex-encoded user-provided id associated with the channel throughout its lifetime.
Examples:
$ curl -X POST http://localhost:5393/v2/node/open_channel \
--header "content-type: application/json" \
--data '{ "value": "500000", "user_channel_id": "0123456789abcdef0123456789abcdef" }' \
| jq .
{
"channel_id": "a1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f90",
"user_channel_id": "0123456789abcdef0123456789abcdef"
}
POST /v2/node/close_channel
Close a Lightning channel between this node and Lexe's LSP.
Request:
The request body should be a JSON object with the following fields:
channel_id: String: The hex-encoded id of the channel to close.
Response:
An empty JSON object {}.
Examples:
$ curl -X POST http://localhost:5393/v2/node/close_channel \
--header "content-type: application/json" \
--data '{ "channel_id": "a1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f90" }' \
| jq .
{}
GET /v2/node/list_clients
List the active clients authorized to control this node — the scoped, revocable
credentials created via create_client or the Lexe mobile app. See
Authentication for background. Revoked and expired
clients are not included.
Request:
Empty.
Response:
clients: A JSON object mapping each client's hex-encoded public key to a client info object with the following fields:client_pk: The hex-encoded ed25519 public key of the client.created_at: When the client was created, in milliseconds since the UNIX epoch.expires_at: Int | null: When the client expires, in milliseconds since the UNIX epoch.nullmeans the client never expires.label: String | null: An optional label for the client.
Examples:
$ curl http://localhost:5393/v2/node/list_clients | jq .
{
"clients": {
"d3f8a1c05e6b47298f0a2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f70": {
"client_pk": "d3f8a1c05e6b47298f0a2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f70",
"created_at": 1772349163000,
"expires_at": 1774941163000,
"label": "backend-server"
},
"2f5c9e1a7b3d84f60c1e9a2b4d6f80a3c5e7091b2d4f60a8c1e3b5d7f9a1c3e5": {
"client_pk": "2f5c9e1a7b3d84f60c1e9a2b4d6f80a3c5e7091b2d4f60a8c1e3b5d7f9a1c3e5",
"created_at": 1772349170000,
"expires_at": null,
"label": null
}
}
}
POST /v2/node/create_client
Create a new client authorized to control this node, returning its public key
and credentials. The credentials can then be passed in the Authorization
header (Bearer <credentials>) to control this node.
WARNING: Anyone with the returned credentials can control this node's funds. Store them somewhere safe.
Request:
The request body should be a JSON object with the following fields:
expires_at: Int(optional): When the client should expire, in milliseconds since the UNIX epoch. Omit or set tonullfor a client that never expires — use carefully.label: String(optional): A label to help you remember what the client is for. Must be at most 64 UTF-8 bytes.
Response:
client_pk: The hex-encoded ed25519 public key of the created client.client_credentials: The credentials string used to authenticate as this client.created_at: When the client was created, in milliseconds since the UNIX epoch.
Examples:
$ curl -X POST http://localhost:5393/v2/node/create_client \
--header "content-type: application/json" \
--data '{ "label": "backend-server", "expires_at": 1774941163000 }' \
| jq .
{
"client_pk": "d3f8a1c05e6b47298f0a2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f70",
"client_credentials": "eyJsZXhlX2F1dGhfdG9rZ...TA0In0",
"created_at": 1772349163000
}
PUT /v2/node/update_client
Update a client's label or expiration.
Omitted fields are left unchanged. To set a label or expiration, pass label
/ expires_at; to clear one, pass clear_label / clear_expiration.
Request:
The request body should be a JSON object with the following fields:
client_pk: String: The hex-encoded public key of the client to update.label: String(optional): Set the client's label. Conflicts withclear_label. Must be at most 64 UTF-8 bytes.clear_label: Bool(optional): Set totrueto remove the client's existing label. Conflicts withlabel. Defaults tofalse.expires_at: Int(optional): Set the client's expiration, in milliseconds since the UNIX epoch. Conflicts withclear_expiration.clear_expiration: Bool(optional): Set totrueto remove the client's expiration so it never expires. Conflicts withexpires_at. Use carefully! Defaults tofalse.
Response:
client: The updated client info object (same fields as the entries returned byGET /v2/node/list_clients).
Examples:
# Relabel a client and extend its expiration
$ curl -X PUT http://localhost:5393/v2/node/update_client \
--header "content-type: application/json" \
--data '{
"client_pk": "d3f8a1c05e6b47298f0a2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f70",
"label": "backend-server-v2",
"expires_at": 1777533163000
}' \
| jq .
{
"client": {
"client_pk": "d3f8a1c05e6b47298f0a2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f70",
"created_at": 1772349163000,
"expires_at": 1777533163000,
"label": "backend-server-v2"
}
}
# Clear the label and make the client never expire
$ curl -X PUT http://localhost:5393/v2/node/update_client \
--header "content-type: application/json" \
--data '{
"client_pk": "d3f8a1c05e6b47298f0a2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f70",
"clear_label": true,
"clear_expiration": true
}' \
| jq .
{
"client": {
"client_pk": "d3f8a1c05e6b47298f0a2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f70",
"created_at": 1772349163000,
"expires_at": null,
"label": null
}
}
POST /v2/node/revoke_client
Permanently revoke a client, making its credentials invalid for authentication. This cannot be undone.
Request:
The request body should be a JSON object with the following fields:
client_pk: String: The hex-encoded public key of the client to revoke.
Response:
client: The revoked client info object (same fields as the entries returned byGET /v2/node/list_clients).
Examples:
$ curl -X POST http://localhost:5393/v2/node/revoke_client \
--header "content-type: application/json" \
--data '{ "client_pk": "d3f8a1c05e6b47298f0a2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f70" }' \
| jq .
{
"client": {
"client_pk": "d3f8a1c05e6b47298f0a2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f70",
"created_at": 1772349163000,
"expires_at": 1774941163000,
"label": "backend-server"
}
}