nodana

Introduction

Use the Nodana API to manage Phoenixd nodes.

Base URL

Use https://api.nodana.io/v1.

All endpoint paths are relative to this base URL. The API determines your account from your API key. Send JSON request bodies with Content-Type: application/json; responses use JSON except for 204 No Content.

Authentication

Use your API key in the Bearer header. Replace YOUR_API_KEY with your key. See Authentication for permissions and key management.

Make a request

Call GET /nodes to list the nodes in your account. A successful response wraps the results in a nodes array.

API basics

Node operations and payments

For invoice creation or sending Lightning payments, call your node's Phoenixd API using its endpoint and Phoenixd password. See Phoenixd API reference for authentication and the official Phoenixd reference.

Choose a resource in the sidebar for endpoint details:

  • Nodes: create and manage your nodes.
  • MPP: create payment challenges and verify Lightning payments with hosted challenge storage and redemption.
  • MPP sessions: accept prepaid deposits, charge usage, receive session events and refund unused balances through a full-access node.
  • L402: create L402 challenges and verify paid credentials through your node.
  • L402 sessions: accept prepaid deposits, meter usage, watch balance updates and refund unused funds.

For a compact plain-text summary of the bearer-key endpoints, see the Nodana API text reference.

Authentication

For direct API integrations, authenticate node operations with an API key in the Authorization header.

Create an API key

In the dashboard, open Developers, then API Keys. Create a key for your integration and select the permissions it needs. Save the full key when it is shown; it cannot be retrieved later.

Send authenticated requests

Send your key as Authorization: Bearer YOUR_API_KEY. Replace the placeholder with your API key.

To use the CLI, sign in with ndn login.

To inspect the current key's ID, account ID, and permissions, call GET /api-key with that Bearer header. This endpoint does not reveal the secret or create a new key.

Permissions

PermissionOperations
Nodes → ReadList nodes, read node details and check available updates
Nodes → WriteCreate, change, start, stop, update or delete nodes
Gateway → ReadRead MPP/L402 session balances and refund status, and watch session events
Gateway → WriteCreate challenges, verify payments, charge usage, close sessions and handle refunds

Choose node and gateway permissions independently. Enabling Write also selects Read for that group. For a payment integration, enable Gateway → Write; you can turn off node permissions if the integration already knows its node ID. Enable Nodes → Read if it also needs to look up nodes.

To use an existing key for MPP or L402, edit the key and enable Gateway Write.

Keys belong to one account. The API uses the key to determine your account; you do not need to send an account ID. The key must include the required permission. Missing, invalid, disabled, or revoked keys and insufficient permissions return 401 Unauthorized. Nodes belonging to another account return 404 Not Found.

Keep keys private

Use keys from your backend or trusted scripts. Store them in secret storage, keep them out of browser code and source control, and revoke or replace them if exposed.

A Nodana API key authenticates calls to the Nodana API. Your node's Phoenixd password is a separate credential for direct calls to its Phoenixd API.

Responses

Node reads and lifecycle operations return the same node object. List responses wrap objects in nodes; creation wraps the node alongside credentials.

Node response fields

FieldTypeDescription
idstringNodana node ID
namestringDisplay name
statusstringNode lifecycle state
endpointUrlstringPhoenixd HTTPS endpoint
failureMessagestring or nullPublic failure message
updateStatusstring or nullUpdate progress or outcome
updateCompletedAtinteger or nullUpdate completion time in Unix seconds
createdAtintegerCreation time in Unix seconds
updatedAtintegerLast change time in Unix seconds

Node states

Node states include provisioning, ready, stopped, cordoned, updating, failed, deleting, and deleted. stopped means the user took the node offline; cordoned means it was suspended because the account ran out of credit.

For shared response handling, see Error handling and Rate limiting.

Error handling

Check the HTTP status before reading a response as a successful result. API errors return an error object containing a stable code and a human-readable message.

Use the code and status for application logic. Keep the message for diagnostics rather than matching its wording.

Status codes

For 400, check the input and the operation's state requirements. For 401, check your key and permissions. For 404, check the node ID and whether it was deleted. For 429, wait for the Retry-After interval.

If credit or billing prevents the operation (402), resolve the account's credit or billing state. Server and upstream failures (500, 502, 503) require checking the outcome before retrying a mutation.

For hosted MPP and L402 endpoints, 402 is also the expected response when creating a payment challenge. During verification, it means the credential was rejected, expired or used for another operation. A 409 with code operation_already_funded means another payment has already funded that purchase.

Malformed JSON or unsupported request content types may be rejected before the endpoint runs. Handle unexpected or non-JSON error bodies as well.

Retry carefully

Retry reads after transient failures using bounded exponential backoff. For 429 responses, follow the rate limiting guidance.

A timeout or connection error does not establish that a mutation failed. Check node state before repeating a lifecycle operation. After an interrupted create request, list nodes before creating another one. Node creation does not accept an idempotency key.

Retry hosted payment verification with the same operationId, challenge and credential to recover its recorded result. Use the operation ID to prevent duplicate delivery in your application. See MPP verification or L402 verification.

For MPP sessions and L402 sessions, retry deposits with the original challenge and proof, and usage charges with the same operation ID and unit count. A 402 session_insufficient_balance means pause delivery until a top-up, then retry that usage operation. A 202 close response means the refund is pending; watch session events and retry the original close request after completion.

A 202 Accepted creation response means provisioning is still in progress. A successful update request also starts background work; inspect the node to determine the final outcome.

Rate limiting

Node operations share a request limit per client IP address. Requests from applications behind the same outbound IP count towards the same limit, even when they use different API keys.

The default policy is 180 requests per 60-second window. Deployment settings may change this limit; use the values returned in a rate-limit response when deciding when to retry.

Rate-limit responses

When the limit is exceeded, the API returns 429 Too Many Requests with these headers:

Retry-After tells you how many seconds to wait before retrying. X-RateLimit-Limit and X-RateLimit-Remaining describe the window, while X-RateLimit-Reset is a reset interval in seconds rather than a Unix timestamp.

These headers are returned on rate-limit errors. Do not rely on receiving them with every successful request.

View response headers

Use curl -i to include response headers when inspecting a request.

Handle throttling

Wait at least the Retry-After interval before retrying, and add a small random delay when multiple workers share an IP. Limit concurrent requests and avoid tight polling loops during node provisioning or updates.

For network failures and other transient errors, use bounded backoff rather than continuously retrying. See Error handling for reconciling requests whose outcome is uncertain.

List nodes

List the active nodes in your account. Deleted nodes are excluded.

GET/nodes

Required permission: Nodes → Read.

Create a node

Create a Phoenixd node. Provisioning continues after the response is returned.

POST/nodes

Required permission: Nodes → Write.

Body parameters

namestringOptional

Node display name, up to 16 characters after trimming. Omit it or leave it blank to use Node n, where n is the account's previous node count plus one, including deleted nodes.

autoLiquiditystringOptional

Choose 2m, 5m, or 10m for the node's automatic liquidity setting.

Defaults to 2m.

accessModestringOptional

Choose limited or full to select which password Nodana stores for dashboard access.

limited stores the restricted password for dashboard reads and invoice creation. full stores the full password so the dashboard can also send payments. Both passwords are returned at creation and can be used to call the node directly.

Defaults to limited.

Save the returned credentials and recovery words securely. They are returned at creation, not by subsequent node reads. Creation requires sufficient account credit and can return 402 Payment Required.

The node is usable once its status is ready.

Get a node

Return a node's details without its passwords or recovery seed.

GET/nodes/{nodeId}

Required permission: Nodes → Read.

Path parameters

nodeIdstringRequired

ID of the node in your account. Use List nodes to find its ID.

Rename a node

Change the display name of a node.

PATCH/nodes/{nodeId}

Required permission: Nodes → Write.

Path parameters

nodeIdstringRequired

ID of the node in your account. Use List nodes to find its ID.

Body parameters

namestringRequired

New display name for the node. Must be non-blank and contain no more than 16 characters after trimming.

Start a node

Start a stopped or failed node. The node starts in the background.

POST/nodes/{nodeId}/start

Required permission: Nodes → Write.

Path parameters

nodeIdstringRequired

ID of the node in your account. Use List nodes to find its ID.

The node must have status stopped or failed. Other states return 400 Bad Request.

Stop a node

Stop a node while retaining its data.

POST/nodes/{nodeId}/stop

Required permission: Nodes → Write.

Path parameters

nodeIdstringRequired

ID of the node in your account. Use List nodes to find its ID.

The node must have status ready. Its payment API is unavailable while stopped, and incoming requests cannot restart it. Stopping a node does not pause billing.

Restart a node

Stop and start an existing node.

POST/nodes/{nodeId}/restart

Required permission: Nodes → Write.

Path parameters

nodeIdstringRequired

ID of the node in your account. Use List nodes to find its ID.

The node must have status ready. Its payment API is temporarily unavailable during the restart.

Check for an update

Return the current Phoenixd version and next supported update.

GET/nodes/{nodeId}/update

Required permission: Nodes → Read.

Path parameters

nodeIdstringRequired

ID of the node in your account. Use List nodes to find its ID.

availableUpdate is null when there is no supported update. currentVersion may be null if its version cannot be determined.

Update a node

Begin the next supported Phoenixd update. The update continues in the background.

POST/nodes/{nodeId}/update

Required permission: Nodes → Write.

Path parameters

nodeIdstringRequired

ID of the node in your account. Use List nodes to find its ID.

The node must have status ready and an available update. The initial response has status updating; it does not indicate that the update has completed. Node data is retained.

Delete a node

Permanently remove a node.

DELETE/nodes/{nodeId}

Required permission: Nodes → Write.

Path parameters

nodeIdstringRequired

ID of the node in your account. Use List nodes to find its ID.

Reconcile outstanding payments, handle remaining funds, and retain recovery information before deleting. Bearer API-key requests do not require a two-factor code.

Create challenge

Create a Lightning payment challenge for a price and description supplied by your backend. Nodana creates the invoice on your node and saves the challenge for verification.

POST/mpp/challenges

Required permission: Gateway → Write.

Authenticate with your Nodana API key. The key must have access to the account that owns nodeId. Keep it in your backend; the payer does not need your API key or node password.

Body parameters

nodeIdstringRequired

ID of the receiving Phoenixd node in your account. Use List nodes to find it. The node must be ready to create invoices.

pricestringRequired

Price in satoshis as a positive integer string, for example "100" for 100 sats. The maximum is "9007199254740". Determine this value in your backend before requesting the challenge.

descriptionstringRequired

Description of the purchase, for example "Premium API access". Must be nonblank, contain no control characters and be at most 500 characters.

realmstringOptional

Payment protection space, usually your API hostname, for example "api.example.com". Defaults to "api.nodana.io". Must be nonblank and at most 255 printable ASCII characters. Set it in your backend when creating the challenge; forward the returned header unchanged.

Return the challenge to the payer

The response is HTTP 402 Payment Required. Forward its WWW-Authenticate header and Cache-Control: no-store to the payer with your own 402 response. Save the returned challengeId against the operation being purchased so your backend can identify it on retry.

The response includes expiresAt, the deadline for submitting payment proof. The payment window is five minutes, capped to the invoice expiry. A payment does not extend this deadline. If an unused challenge expires, request a new one. Repeating a creation request creates another invoice.

When the payer retries with an Authorization: Payment … header, use Verify an MPP payment. Serve the protected result only after successful verification.

Verify payment

Verify the payer's credential against the original challenge and redeem the payment once. Nodana checks the payment proof and expiry and prevents the same payment from being redeemed again, including concurrent submissions.

POST/mpp/verify

Required permission: Gateway → Write.

Authenticate with the same account's Nodana API key. Send the payer's payment credential in the JSON body; keep the request's Authorization header for your API key.

Body parameters

nodeIdstringRequired

ID of the receiving node used to create the challenge. The API key must have access to its account.

challengeIdstringRequired

Identifier returned when the challenge was created. Use the challenge associated with the operation being purchased in your application. It must match the challenge in the credential.

operationIdstringRequired

Your application's unique identifier for this purchase, for example "order-123". Use 1–128 ASCII letters, digits, hyphens, underscores, colons or dots. Keep it unchanged when retrying verification. Use a different value for each new purchase.

credentialstringRequired

Complete value of the payer's Authorization header, including the Payment prefix. Treat the credential as a secret and keep it out of logs.

Serve the paid result

A 200 OK response means the payment was verified and redeemed. Run the protected operation only after this response and attach the returned Payment-Receipt header to your successful response. Set Cache-Control: no-store.

The JSON response identifies the challenge, payment hash and amount paid in sats. The receipt references the payment hash and does not expose the payment preimage.

Handle failed verification

An invalid credential, expired unused challenge or payment redeemed for another operation returns 402 Payment Required. Do not serve the protected result. To offer another payment attempt, create a fresh challenge and return it to the payer.

If verification times out, retry with the same nodeId, challengeId, operationId and credential. A successful retry returns the original result and redemption timestamp, including after the payment window expires.

Use operationId to serve each purchase once in your application. A repeated success acknowledges the same purchase; it does not authorise another delivery. If delivery fails, use your application record to recover that operation.

Trying to fund the same operation with a different payment returns 409 Conflict with code operation_already_funded. Check the original purchase before requesting another payment.

Create session challenge

Create an MPP Lightning session challenge for a deposit, top-up, bearer request or close action. New sessions and top-ups require a ready node configured with full access so Nodana can refund the unused balance.

POST/mpp/sessions/challenges

Required permission: Gateway → Write. The node and session must belong to the API key's account. Keep the key in your backend.

Body parameters

nodeIdstringRequired

Receiving node in your account.

actionstringOptional

One of open, topUp, bearer or close. Defaults to open.

pricestringOptional

Positive integer sats charged per unit. Required for open; omit for other actions.

descriptionstringOptional

Purchase description. Required for open, nonblank and at most 500 characters with no control characters.

unitTypestringOptional

Unit label such as token or request. Defaults to request. Set only when opening a session.

depositstringOptional

Exact deposit in sats, at least one unit's price. Defaults to 1,000 units. Set for open or topUp only. Maximum deposit and cumulative session funding: 9007199254740 sats.

sessionIdstringOptional

Existing session ID. Required for bearer, topUp and close; omit for open.

realmstringOptional

Payment protection space, usually your API hostname, for example "api.example.com". Defaults to "api.nodana.io". Must be nonblank and at most 255 printable ASCII characters. Supply the same realm when creating each session action's challenge, including top-ups, bearer requests and close actions. Forward the returned header unchanged.

Forward WWW-Authenticate and Cache-Control: no-store with HTTP 402 to the payer. The challenge expires within five minutes. Deposit challenges include a fresh invoice; bearer and close challenges do not request another payment.

The unit price is fixed when the session opens. For later actions, Nodana uses the stored price and description. Pass the payer's credential to Verify session.

Top-up payment windows are capped to the session's remaining idle time. If a paid top-up cannot be credited because its window expired or the session closed, Verify session supports recovery with a fresh return invoice.

Open sessions close after five minutes without a successful bearer action, top-up or usage charge. Gateway Read keys cannot create or verify sessions. A limited-access node returns 409 session_requires_full_access for deposits.

Verify session

Verify an MPP session credential and apply its open, bearer, top-up or close action once. Verification checks the exact issued challenge, payment proof, node and session.

POST/mpp/sessions/verify

Required permission: Gateway → Write. The node and session must belong to the API key's account. Keep the key in your backend.

Body parameters

nodeIdstringRequired

Receiving node used to create the challenge.

challengeIdstringRequired

Identifier of the issued session challenge.

credentialstringRequired

Complete payer Authorization value, including the Payment prefix. Keep this out of logs.

returnInvoicestringOptional

Optional fresh amountless return invoice for refunding a paid top-up that cannot be credited. This is a gateway request field, separate from the MPP credential payload. Supply it with a top-up proof, either proactively or when retrying 409 session_topup_refund_required. Use the same network and an expiry beyond five minutes; do not reuse the opening return invoice.

Send your Nodana API key in the gateway request's Authorization header. Send the payer's credential in the JSON body.

  • Open: the payer supplies the deposit preimage and an amountless return invoice on the same network. The return invoice must outlive the five-minute idle timeout; a 30-day expiry is recommended. Use a fresh return invoice for each session.
  • Bearer: verify ownership before handling a subsequent request. Verification alone does not deduct usage.
  • Top up: credit a fresh deposit once. Return {"status":"ok"} to the payer with the payment receipt; resume any paused stream on its original connection.
  • Close: stop usage and attempt to refund the remaining deposit. A zero balance skips the outgoing payment.

A completed action returns 200 OK and Payment-Receipt. Attach the receipt to your response. Identical credential retries return the recorded action result, including after challenge expiry. Before delivering metered content, use Record usage; replaying an old successful action does not authorise extra delivery.

Close can return 202 Accepted while the refund is pending or its outcome is unknown. Keep the operation pending and use Session events for updates. After completion, retry the same close credential to retrieve the final result and receipt. The session remains closed if the refund fails; it never becomes spendable again.

Invalid proofs, expired unused challenges and closed sessions return 402. An already registered return invoice returns 409 return_invoice_already_used.

Recover a paid top-up

If a top-up reaches verification after its payment deadline or session closure, it cannot add spendable credit. Without a fresh returnInvoice, verification returns 409 session_topup_refund_required. Retry the same challenge and credential with a fresh invoice in the gateway JSON body. The paid proof remains recoverable after expiry; invalid proofs are always rejected.

Recovery returns action: "topUpRefund", originalSessionId and a new sessionId for the closed refund entry. Use that new ID with Session events or Reconcile refund. A pending or unknown refund returns 202; repeat the same verification request for its final result. Recovery does not authorise content or return a successful MPP top-up receipt. It leaves the original session and refund unchanged.

Record usage

Atomically deduct service units from a verified session. The gateway uses the session's original unit price and prevents concurrent requests from spending the same balance.

POST/mpp/sessions/{sessionId}/usage

Required permission: Gateway → Write. The node and session must belong to the API key's account. Keep the key in your backend.

Body parameters

nodeIdstringRequired

Receiving node for this session.

operationIdstringRequired

Unique ID for this unit or batch, such as stream-42:chunk-1. Use 1–128 ASCII letters, digits, hyphens, underscores, colons or dots. Keep it unchanged on retry.

unitsstringRequired

Number of units to charge, as a positive integer string.

The path sessionId is the payment hash returned when the session opened. Verify the payer's session credential before reporting usage. Charge before delivering each chunk or batch. The cost is units × price, using integer sats.

Retrying the same operation and unit count returns the original result without another deduction. Reusing an operation ID with different units or a different session returns 409. Deduplicate content delivery in your app as well.

Insufficient balance returns 402 session_insufficient_balance without recording or charging the operation. Pause delivery, emit the payer-facing MPP payment-need-topup event and wait for a top-up through Session events. Resume by charging the same operation again once funded. A closed or idle session returns 402 session_closed.

Each successful new usage operation extends the session's five-minute idle deadline. Status reads and SSE connections do not extend it.

Get session

Read the current session balance, lifecycle and refund status. The path sessionId is the original deposit's payment hash.

GET/mpp/sessions/{sessionId}

Required permission: Gateway → Read. The node and session must belong to the API key's account. Keep the key in your backend.

Use this for an initial read or troubleshooting. Use Session events to wait for changes rather than repeatedly requesting this endpoint.

status is open or closed. Refund status is null before closure, then pending, sending, unknown, succeeded, failed or skipped. unknown means a payment may have been sent but has not been confirmed; it must not be blindly retried.

A session snapshot does not authorise resource access. Verify the payer's credential and charge usage before delivery.

Session events

Receive session, balance and refund updates over Server-Sent Events. The stream starts with a current snapshot, then pushes changes without balance polling.

GET/mpp/sessions/{sessionId}/events

Required permission: Gateway → Read. The node and session must belong to the API key's account. Keep the key in your backend.

Open this stream from your backend using your API key. Each session event contains the same fields as Get session. revision increases when the session changes. Reconnect after a disconnect to receive the current snapshot. Up to five event connections are allowed per session.

For a metered response, keep the payer's original stream open while its balance is insufficient. Emit payment-need-topup with sessionId, balanceSpent and balanceRequired. Watch this Nodana event stream; after a top-up, retry your usage charge before emitting more content. The payer-facing stream should stop waiting after 60 seconds if it cannot resume, using the MPP session-timeout event.

For example, write this SSE frame to the payer's existing text/event-stream response, followed by a blank line:

event: payment-need-topup
data: {"sessionId":"<deposit-payment-hash>","balanceSpent":100,"balanceRequired":20}

balanceSpent is the total sats spent from the session. balanceRequired is the full cost of the next unit or batch, not the amount missing from the balance. See the MPP solution guide for a JavaScript example that writes the event using a ReadableStream controller.

The developer’s backend serves the payer's stream and emits its final MPP payment-receipt event with method, reference, status, timestamp, spent and units before [DONE]. Count only the units charged for that stream. Nodana's session events are backend state notifications, not the payer-facing content stream.

Keep the stream open for pending refunds and close it when the backend no longer needs updates. Keeping SSE open does not extend the five-minute session idle timeout.

Reconcile refund

Check an uncertain refund against the node's outgoing payment record. This endpoint only reads the payment outcome; it never sends or retries a refund.

POST/mpp/sessions/{sessionId}/refund/reconcile

Required permission: Gateway → Write. The node and session must belong to the API key's account. Keep the key in your backend.

Use this when a session reports refundStatus: "unknown", for example after a lost node response. A confirmed outcome is saved and pushed through Session events.

If Phoenixd cannot establish an outcome, status stays unknown. A missing payment record is not treated as proof of failure. Inspect the node's outgoing payments before taking further action; do not issue another refund blindly. A definitive failed refund leaves the session closed.

The JSON response is the current session snapshot. Once the refund is resolved, retry the original close credential to retrieve its final MPP receipt.

Create challenge

Create a Lightning payment challenge for a price and description supplied by your backend. Nodana creates the invoice on your node and saves the challenge for verification.

POST/l402/challenges

Required permission: Gateway → Write.

Authenticate with your Nodana API key. The key must have access to the account that owns nodeId. Keep it in your backend; the payer does not need your API key or node password.

Body parameters

nodeIdstringRequired

ID of the receiving Phoenixd node in your account. Use List nodes to find it. The node must be ready to create invoices.

pricestringRequired

Price in satoshis as a positive integer string, for example "100" for 100 sats. The maximum is "9007199254740". Determine this value in your backend before requesting the challenge.

descriptionstringRequired

Description of the purchase, for example "Premium API access". Must be nonblank, contain no control characters and be at most 500 characters.

Return the challenge to the payer

The response is HTTP 402 Payment Required. Forward its WWW-Authenticate header and Cache-Control: no-store to the payer with your own 402 response. Save the returned challengeId against the operation being purchased so your backend can identify it on retry.

The response includes expiresAt, the deadline for submitting payment proof. The payment window is five minutes, capped to the invoice expiry. A payment does not extend this deadline. If an unused challenge expires, request a new one. Repeating a creation request creates another invoice.

When the payer retries with an Authorization: L402 <macaroon>:<preimage> header, use Verify payment. Serve the protected result only after successful verification.

Verify payment

Verify the payer's credential against the original challenge and redeem the payment once. Nodana checks the payment proof and expiry and prevents the same payment from being redeemed again, including concurrent submissions.

POST/l402/verify

Required permission: Gateway → Write.

Authenticate with the same account's Nodana API key. Send the payer's payment credential in the JSON body; keep the request's Authorization header for your API key.

Body parameters

nodeIdstringRequired

ID of the receiving node used to create the challenge. The API key must have access to its account.

challengeIdstringRequired

Identifier returned when the challenge was created. Use the challenge associated with the operation being purchased in your application. The macaroon in the credential must belong to this challenge.

operationIdstringRequired

Your application's unique identifier for this purchase, for example "order-123". Use 1–128 ASCII letters, digits, hyphens, underscores, colons or dots. Keep it unchanged when retrying verification. Use a different value for each new purchase.

credentialstringRequired

Complete value of the payer's Authorization header, in the form L402 <macaroon>:<preimage>. The macaroon comes from the challenge and the preimage is obtained by paying its invoice. Treat the credential as a secret and keep it out of logs.

Serve the paid result

A 200 OK response means the payment was verified and redeemed. Run the protected operation only after this response and set Cache-Control: no-store on your response.

The JSON response identifies the challenge, payment hash and amount paid in sats. It does not expose the payment preimage.

Handle failed verification

An invalid credential, expired unused challenge or payment redeemed for another operation returns 402 Payment Required. Do not serve the protected result. To offer another payment attempt, create a fresh challenge and return it to the payer.

If verification times out, retry with the same nodeId, challengeId, operationId and credential. A successful retry returns the original result and redemption timestamp, including after the payment window expires.

Use operationId to serve each purchase once in your application. A repeated success acknowledges the same purchase; it does not authorise another delivery. If delivery fails, use your application record to recover that operation.

Trying to fund the same operation with a different payment returns 409 Conflict with code operation_already_funded. Check the original purchase before requesting another payment.

Create session challenge

Create a deposit or top-up invoice for a Nodana L402 session. The node must be ready and configured with full access so Nodana can refund unused funds.

POST/l402/sessions/challenges

Required permission: Gateway → Write. The node and session must belong to the API key's account. Keep the key in your backend.

Body parameters

nodeIdstringRequired

Receiving node in your account.

actionstringOptional

open or topUp. Defaults to open. Use the original paid credential with Verify session for later bearer access and closing.

pricestringOptional

Positive integer sats per unit. Required for open; omit for topUp.

descriptionstringOptional

Purchase description. Required for open, nonblank, at most 500 characters and no control characters. Omit for topUp.

unitTypestringOptional

Unit label such as token or request. Defaults to request. Set only for open.

depositstringOptional

Exact deposit in sats, at least one unit’s price. Defaults to 1,000 units. Maximum deposit and cumulative session funding: 9007199254740 sats.

sessionIdstringOptional

Existing session ID, required for topUp. Omit for open.

Forward the returned WWW-Authenticate header and Cache-Control: no-store with HTTP 402 to the payer. Save challengeId against the intended session action. The invoice payment window is five minutes, capped to invoice expiry. Repeating creation creates another invoice.

After payment, verify the complete L402 credential through Verify session. Opening also requires an amountless return invoice supplied by the payer. Keep the original paid opening credential for later access and closing. It expires 30 days after creation; the session closes after five minutes without successful bearer verification, top-up or usage.

The unit price is fixed at opening. Top-ups use the session's stored price and description. A top-up credential only funds its assigned session; use the original opening credential to authorise access.

Top-up payment windows are capped to the session's remaining idle time. If a paid top-up cannot be credited after expiry or closure, Verify session can refund it using a fresh return invoice.

These are Nodana session operations. A payer client must support your application's session, top-up and refund flow in addition to paying L402 invoices.

Verify session

Verify an L402 payment to open or top up a Nodana session, or present the original opening credential to authorise access or close the session.

POST/l402/sessions/verify

Required permission: Gateway → Write. The node and session must belong to the API key's account. Send the Nodana key in Authorization: Bearer … and the payer's complete Authorization: L402 … value in the JSON body.

Body parameters

nodeIdstringRequired

Receiving node used to create the session.

actionstringOptional

open, topUp, bearer or close. Defaults to open.

credentialstringRequired

Complete payer Authorization value including the L402 prefix. For open or topUp, supply the credential for that invoice. For bearer and close, supply the original paid opening credential. Keep credentials out of logs.

challengeIdstringOptional

Required for open and topUp: the challenge associated with that action. Omit for bearer and close.

sessionIdstringOptional

Required for bearer and close. Optional for topUp, where it must match the session assigned to the challenge. Omit for open.

returnInvoicestringOptional

Required for open; optional for topUp; omit for bearer and close. A fresh amountless or zero-amount Lightning invoice supplied by the payer, on the same network and valid beyond five minutes. A 30-day expiry is recommended for opening. For top-ups, this invoice enables a refund if the payment cannot be credited. Each return invoice can be registered only once per receiving node; do not reuse the opening invoice for a top-up refund.

  • Open: verify the deposit and register the payer's return invoice. The response includes sessionId. Retrying the same opening proof and return invoice returns the recorded result without crediting twice.
  • Top up: credit the fresh invoice once to its assigned session. Use the same challenge and proof on retry. Reusing a top-up credential does not add credit again.
  • Bearer: present the original opening credential on later requests. This checks session ownership and refreshes activity without charging usage. Call Record usage before delivering content.
  • Close: present the original opening credential and sessionId. This freezes the balance and attempts a refund. Repeated or concurrent close requests cannot send the refund again.

Successful actions return 200 OK with JSON. A top-up result has status: "ok". Close can return 202 Accepted while the refund is pending or unknown. Use Session events, then retry the same close request after completion for the final result. A failed refund leaves the session closed.

The original opening credential expires after 30 days. Active access also requires an open session with activity in the last five minutes. Reads, SSE connections and retries of an already completed deposit do not extend session activity. Completed deposit and close retries still require valid proof and return their recorded result after the payment window expires.

Invalid proofs and closed or idle sessions return 402. An expired unused challenge returns 402 payment_challenge_expired. An already registered return invoice returns 409 return_invoice_already_used.

Recover a paid top-up

If the top-up's payment window expires or its session closes before verification, the gateway can refund the payment using a fresh returnInvoice. Without that invoice, it returns 409 session_topup_refund_required. Retry the same challenge and paid credential with the invoice. Expired top-up proofs can authorise this refund, but cannot authorise new access or credit.

Recovery returns action: "topUpRefund", originalSessionId and a new sessionId for the closed refund entry. Watch Session events using the new ID. A pending or unknown refund returns 202; retry the same verification request for the final result. Use the new ID for Reconcile refund if needed. Recovery leaves the original session and its refund unchanged. Concurrent retries cannot refund twice.

Record usage

Atomically deduct service units from a verified session. The gateway uses the session's original unit price and prevents concurrent requests from spending the same balance.

POST/l402/sessions/{sessionId}/usage

Required permission: Gateway → Write. The node and session must belong to the API key's account. Keep the key in your backend.

Body parameters

nodeIdstringRequired

Receiving node for this session.

operationIdstringRequired

Unique ID for this unit or batch, such as stream-42:chunk-1. Use 1–128 ASCII letters, digits, hyphens, underscores, colons or dots. Keep it unchanged on retry.

unitsstringRequired

Number of units to charge, as a positive integer string.

The path sessionId is the payment hash returned when the session opened. Verify the payer's session credential before reporting usage. Charge before delivering each chunk or batch. The cost is units × price, using integer sats.

Retrying the same operation and unit count returns the original result without another deduction. Reusing an operation ID with different units or a different session returns 409. Deduplicate content delivery in your app as well.

Insufficient balance returns 402 session_insufficient_balance without recording or charging the operation. Pause delivery and wait for a top-up through Session events. Resume by charging the same operation again once funded. A closed or idle session returns 402 session_closed.

Each successful new usage operation extends the session's five-minute idle deadline. Status reads and SSE connections do not extend it.

Get session

Read the current session balance, lifecycle and refund status. The path sessionId is the original deposit's payment hash.

GET/l402/sessions/{sessionId}

Required permission: Gateway → Read. The node and session must belong to the API key's account. Keep the key in your backend.

Use this for an initial read or troubleshooting. Use Session events to wait for changes rather than repeatedly requesting this endpoint.

status is open or closed. Refund status is null before closure, then pending, sending, unknown, succeeded, failed or skipped. unknown means a payment may have been sent but has not been confirmed; it must not be blindly retried.

A session snapshot does not authorise resource access. Verify the payer's credential and charge usage before delivery.

Session events

Receive session, balance and refund updates over Server-Sent Events. The stream starts with a current snapshot, then pushes changes without balance polling.

GET/l402/sessions/{sessionId}/events

Required permission: Gateway → Read. The node and session must belong to the API key's account. Keep the key in your backend.

Open this stream from your backend using your API key. Each session event contains the same fields as Get session. revision increases when the session changes. Reconnect after a disconnect to receive the current snapshot. Up to five event connections are allowed per session.

When a usage charge returns 402 session_insufficient_balance, pause delivery and use your application's client flow to request a top-up. Create a fresh L402 top-up challenge, verify its paid credential, then retry the paused usage operation after a funded session event. A session event alone does not authorise content delivery.

These events are backend notifications for Nodana sessions. Your application's client handles top-up prompts, return invoices and close requests. Keep the Nodana API key out of the payer's browser or client.

Keep the stream open for pending refunds and close it when the backend no longer needs updates. Keeping SSE open does not extend the five-minute session idle timeout.

Reconcile refund

Check an uncertain refund against the node's outgoing payment record. This endpoint only reads the payment outcome; it never sends or retries a refund.

POST/l402/sessions/{sessionId}/refund/reconcile

Required permission: Gateway → Write. The node and session must belong to the API key's account. Keep the key in your backend.

Use this when a session reports refundStatus: "unknown", for example after a lost node response. A confirmed outcome is saved and pushed through Session events.

If Phoenixd cannot establish an outcome, status stays unknown. A missing payment record is not treated as proof of failure. Inspect the node's outgoing payments before taking further action; do not issue another refund blindly. A definitive failed refund leaves the session closed.

The JSON response is the current session snapshot. Once the refund is resolved, retry the original close request to retrieve its final result.

Phoenixd API reference

Use your Phoenixd API to control the node itself: creating Lightning invoices, checking balances and payments, and sending payments. These requests go to your node's endpoint, not to the Nodana API.

Nodana gives each node an endpoint and Phoenixd credentials when you create it. Use the node's password to authenticate requests to that endpoint. Keep the password in your backend or a trusted secret store; do not put it in browser code.

Use the restricted password (http-password-limited-access) for creating invoices, decoding them and reading payment information. Sending payments requires the full-access password (http-password).

The official Phoenixd API reference documents the available endpoints, request fields, and responses.

Example: list payments

Use your node's endpoint and Phoenixd password to list incoming payments. This example includes Phoenixd's all, limit, and offset options:

Use /payments/outgoing to list outgoing payments.

Replace the example URL with your node's endpoint and the placeholder password with its Phoenixd password. Keep the password out of shared scripts and logs.

Use the Nodana API for hosting and lifecycle actions such as creating, listing, starting, stopping, updating, and deleting nodes. A Nodana API key manages Nodana resources and is not a Phoenixd password.

Webhooks

Receive a signed JSON POST when your Phoenixd node receives a payment. Each node supports one webhook endpoint.

Add an endpoint

  1. Create a backend route that accepts POST requests at a public HTTPS URL, such as https://example.com/webhooks/nodana. Use the final URL without redirects. For local testing, use a public HTTPS tunnel.
  2. Open your node in the Nodana dashboard, select Webhooks, and click Add endpoint.
  3. Enter your URL and an optional description, then click Add endpoint.
  4. Copy the signing secret and store it in your backend, for example as NODANA_WEBHOOK_SECRET. It is only shown once. If lost, delete and recreate the endpoint, then update your receiver.

Receive an event

The request body is the Phoenixd event payload. A received payment includes type, amountSat and paymentHash.

HeaderPurpose
X-Nodana-SignatureSignature used to verify the raw request body
X-Nodana-Event-IdDelivery ID for detecting repeated deliveries
X-Nodana-Event-TypeEvent type, such as payment_received

Verify the signature before acting on the event. Handle payment_received and tolerate additional payload fields. Acknowledge verified event types you do not use with a 2xx response.

Verify the signature

Use the entire signing secret, including its whsec_ prefix, as a UTF-8 HMAC-SHA256 key. Compare the digest of the exact request body bytes with the hex value in X-Nodana-Signature using a constant-time comparison.

Verify the raw body before parsing it as JSON. The example helper accepts a Buffer and the signature header.

Preserve the raw body in your framework: parsing and re-serialising JSON changes the signed bytes. Reject invalid signatures before processing the payload. Keep the secret out of browser code and logs.

Acknowledge deliveries

Verify the signature, then safely store or queue the event before returning 2xx. Return an error if storage fails so Nodana can retry. Keep slow processing outside the receiver: deliveries time out after 10 seconds.

Store X-Nodana-Event-Id durably with a uniqueness constraint and acknowledge repeated accepted deliveries with 2xx. Also prevent duplicate payment fulfilment using the node and paymentHash. Do not rely on delivery order.

Failed deliveries are retried up to three times, after approximately 1 minute, 5 minutes, and 30 minutes following each failure.

Test and troubleshoot

Create and pay an invoice on your node, then check your receiver and the node's Webhooks delivery history. A successful delivery shows delivered with your 2xx response.

  • No delivery: check that the endpoint is enabled and publicly reachable over HTTPS. Confirm the exact path accepts POST without redirects or browser authentication.
  • Signature verification fails: check the signing secret and confirm the raw body is preserved before JSON parsing.
  • Timeouts or repeated deliveries: acknowledge after durable storage and move slow work to a worker. Make sure duplicate events cannot fulfil a payment twice.
  • Missed payments during an outage: reconcile with your node. Events received while the endpoint is disabled are not replayed when it is re-enabled.

Delivery history is retained for three days. Keep your own payment records; deleting an endpoint also removes its delivery history.