API documentation
Base URL: https://lightning-swap.com
· Authenticated methods under /api/v2
FixedFloat-compatible — subset of ff.io/api
Lightning Swap implements the same paths, headers, HMAC signing, response envelope,
currency codes, and order object shape as the FixedFloat v2 API so existing FixedFloat
clients (and tools built against it) can point at us with minimal changes.
We are intentionally a subset: only fixed-rate pay-in → Lightning BTC
(BTCLN), and only the methods needed to quote, create, poll, and refund.
Surfaces we do not implement are listed under Not supported.
Getting started
- Create an account and open the API tokens page.
- Generate a key. Copy the Lightning Swap Connect URI (or the API key and API secret it carries) from the API tokens page.
- Sign every authenticated POST with HMAC-SHA256 over the exact JSON body string.
- Create orders with
type=fixed,direction=to,toCcy=BTCLN, and a BOLT11 intoAddress. - Poll
/orderuntilDONE,EXPIRED, orEMERGENCY. On emergency, call/emergencywithchoice=REFUND.
Lightning Swap Connect (LSC) URI
Lightning Swap Connect is OpenReceive's compact, server-only format for configuring an authenticated swap API endpoint. One URI replaces a provider's HTTPS base URL, API key, and API secret. The API tokens page shows a ready-made LSC URI for every key you create — copy it and hand it to your integration as a single secret.
lightning+swapconnect://lightning-swap.com?key=<API key>&secret=<API secret>
That URI resolves to:
| Value | Result |
|---|---|
| HTTPS API base URL | https://lightning-swap.com/ |
| API key | Sent as X-API-KEY |
| API secret | HMAC key for X-API-SIGN |
The base URL is the root; authenticated methods still live under /api/v2 — see
Authentication. LSC carries no extra privileges: it is the same
key and secret in one string.
LSC is not an NWC replacement. NWC connects OpenReceive to the receive-only Lightning wallet. LSC connects OpenReceive to an optional service — such as Lightning Swap — that accepts another asset and swaps it into a Lightning invoice.
URI syntax
lightning+swapconnect://host[/path]?key=KEY&secret=SECRET
| Component | Required | Meaning |
|---|---|---|
| Scheme | Yes | Exactly lightning+swapconnect |
| Host | Yes | Swap provider HTTPS host |
| Port | No | Explicit HTTPS port |
| Path | No | Swap provider API base path; / is the default |
key | Yes | Provider API key |
secret | Yes | Provider API secret |
- The URI must not contain user information or a fragment.
- Each required query parameter must appear exactly once.
- Unknown parameters are rejected, so a misspelling cannot silently change configuration.
-
Query names and values use standard URI percent-encoding. Producers should use a URL
implementation rather than concatenating strings; the OpenReceive Node package exports
formatLscUri()for this.
LSC v0.1 defines one swap-provider API contract. Every configured provider is assumed to implement that contract, so the URI has no selector or negotiation field.
Endpoint mapping
The custom scheme always maps to HTTPS:
lightning+swapconnect://HOST[:PORT]/PATH
│
└── https://HOST[:PORT]/PATH/
Plain HTTP cannot be expressed. The parsed base path always has a trailing slash. A provider
identifier is derived from the lower-case hostname, optional port, and path by replacing
unsupported characters with -. Two configured URIs may not derive the same
identifier.
Environment variables
Swap providers are configured with LSC_URI_PRIMARY and optional
LSC_URI_BACKUP. While the primary answers, OpenReceive uses only the primary;
the backup is consulted only when the primary is down.
Security requirements
An LSC URI is a bearer credential
Anyone who obtains it can exercise whatever permissions and budget the provider assigned to that key.
- Keep LSC URIs on the server and out of browser bundles, logs, exception messages, screenshots, analytics, shell history, and committed files.
- Store the complete URI as one secret. Do not split it into public and secret fragments.
- Give each application a separate provider key with the smallest available permissions and budget.
- Rotate the key and secret if the URI is exposed — revoke it on the API tokens page and create a new one.
- Redact the complete value. Redacting only
secretstill exposes the API key and provider identity. - Do not place an LSC URI in a link, QR code, or browser address bar. URI credentials can leak through history, telemetry, referrers, clipboard managers, and process inspection.
NWC intentionally defines a connection URI for a client and wallet service with cryptographic keys. LSC packages conventional HTTPS API credentials; it does not add end-to-end encryption beyond TLS and does not define a provider authorization handshake.
Authentication
Authenticated requests send JSON (UTF-8) with these headers:
| Header | Value |
|---|---|
Content-Type | application/json; charset=UTF-8 |
X-API-KEY | Your API key (UUID public key) |
X-API-SIGN | HMAC-SHA256(secret, raw_body) as lowercase hex |
Sign the exact bytes of the request body. For empty-body calls such as /ccies,
POST the literal string {} and sign that — do not sign an empty string.
cURL — sign and call
BODY='{"type":"fixed","fromCcy":"SOL","toCcy":"BTCLN","direction":"to","amount":"0.00004243"}'
SIGN=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$API_SECRET" | awk '{print $2}')
curl -sS https://lightning-swap.com/api/v2/price \
-H "Content-Type: application/json; charset=UTF-8" \
-H "X-API-KEY: $API_KEY" \
-H "X-API-SIGN: $SIGN" \
-d "$BODY"
Python — sign and call
import hashlib, hmac, json, urllib.request
def ls_post(path, payload, api_key, api_secret):
body = json.dumps(payload, separators=(",", ":"))
sign = hmac.new(api_secret.encode(), body.encode(), hashlib.sha256).hexdigest()
req = urllib.request.Request(
f"https://lightning-swap.com{path}",
data=body.encode(),
headers={
"Content-Type": "application/json; charset=UTF-8",
"X-API-KEY": api_key,
"X-API-SIGN": sign,
},
method="POST",
)
with urllib.request.urlopen(req) as resp:
return json.load(resp)
# Empty-body catalog call — body must be "{}"
print(ls_post("/api/v2/ccies", {}, API_KEY, API_SECRET))
Canonical JSON matters for the signature: whatever string you hash must be identical to the bytes on the wire. Prefer a compact encoder (no insignificant whitespace) and reuse that string for both signing and the POST body.
Request limits
Weight budget: 900 units per UTC minute per API token.
| Endpoint | Weight |
|---|---|
POST /api/v2/create | 50 |
| All other authenticated API calls | 1 |
GET /rates/fixed.xml | 0 (public, no auth) |
- Exceeding the minute budget returns
code: 429— Rate limit exceeded. - When used weight in the current minute reaches 150, further
/createcalls are blocked (Create rate limit gate reached).
Response envelope
Every JSON method returns:
{
"code": 0,
"msg": "OK",
"data": { }
}
| Field | Type | Description |
|---|---|---|
code | number | 0 = success. Non-zero mirrors HTTP intent (401, 404, 409, 429, 503, or 400). |
msg | string | Human-readable status or error. |
data | object / array / boolean / null | Payload. On auth failures often null. On /emergency success always a boolean. |
For /price, FixedFloat-style validation often still returns code: 0 with
data.errors[] populated (e.g. LIMIT_MIN, OFFLINE_FROM).
Treat a non-empty errors array as “do not create”.
Typical integration flow
GET /rates/fixed.xml— cache indicative rates and min/max (optional; no auth).POST /api/v2/ccies— confirm pay-in codes andrecv/send.POST /api/v2/price— quotedirection=tofor a Lightning amount (orfromfor a deposit amount).POST /api/v2/create— binding order; storedata.id+data.token; showfrom.address/from.amountto the payer.POST /api/v2/order— poll until terminal.- If
status=EMERGENCY,POST /api/v2/emergencywithchoice=REFUNDand a refund address on the deposit network; poll untilDONEwithback.tx.id.
/create is authoritative. XML rates and /price are for UX and limits —
the deposit amount on the create response is what the payer must send.
GET /rates/fixed.xml
Public FixedFloat-shaped XML of all online pairs. No API key, no signature, no weight cost.
We do not expose /rates/float.xml (float rate type is unsupported).
Parameters: none.
Fields per <item>
| Field | Type | Description |
|---|---|---|
<from> | string | Pay-in currency code |
<to> | string | Always BTCLN for Lightning Swap |
<in> / <out> | number | Reference amounts defining the rate |
<amount> | number | Reserve hint for to (not a hard max) |
<tofee> | string | Lightning network fee folded into quotes, not included in out |
<minamount> / <maxamount> | string | Min/max pay-in for create |
Example (live shape — SOL → BTCLN)
<rates>
<item>
<from>SOL</from>
<to>BTCLN</to>
<in>1</in>
<out>0.00117230</out>
<amount>0.00320351</amount>
<tofee>0.00000700 BTCLN</tofee>
<minamount>0.02500000 SOL</minamount>
<maxamount>1.35281076 SOL</maxamount>
</item>
…
</rates>
Sample captured from a Lightning Swap exercise run against a live rates feed.
POST /api/v2/ccies
Currency catalog with FixedFloat field names. Pay-in assets have recv=1;
Lightning payout is BTCLN with send=1.
Body: {} (sign that object). Weight: 1.
Response fields
| Field | Type | Description |
|---|---|---|
data[].code | string | Public code used as fromCcy / toCcy |
data[].coin | string | Asset ticker (BTC, SOL, USDT, …) |
data[].network | string | Chain / Lightning network (SOL, ETH, TRX, LN) |
data[].name | string | Display name |
data[].recv | 0|1 | Accepts deposits from client |
data[].send | 0|1 | Can send to client |
data[].tag | string|null | Memo / destination-tag field name when required |
data[].contract | string|null | Token contract when not native |
data[].logo / color / priority | — | Display metadata |
Example response (trimmed)
{
"code": 0,
"msg": "OK",
"data": [
{
"code": "SOL",
"coin": "SOL",
"network": "SOL",
"name": "Solana",
"recv": 1,
"send": 0,
"tag": null,
"contract": null,
"priority": 0,
"logo": "https://lightning-swap.com/assets/images/coins/svg/sol.svg",
"color": "#a364fc"
},
{
"code": "USDTTRC",
"coin": "USDT",
"network": "TRX",
"name": "Tether (TRC20)",
"recv": 1,
"send": 0,
"tag": null,
"contract": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
"priority": 4,
"logo": "https://lightning-swap.com/assets/images/coins/svg/usdttrc.svg",
"color": "#53ae94"
},
{
"code": "BTCLN",
"coin": "BTC",
"network": "LN",
"name": "Bitcoin (Lightning)",
"recv": 0,
"send": 1,
"tag": null,
"contract": null,
"priority": 0,
"logo": "https://lightning-swap.com/assets/images/coins/svg/btcln.svg",
"color": "#9157ff"
}
]
}
POST /api/v2/price
Quote a pair. Use before create to show the user what they will pay / receive.
Only type=fixed is supported. Both direction=from and direction=to work.
Weight: 1.
Parameters
| Name | Type | Description | |
|---|---|---|---|
type | required | string | Must be fixed |
fromCcy | required | string | Pay-in code (SOL, USDTTRC, …) |
toCcy | required | string | Must be BTCLN |
direction | required | string | from = amount is pay-in; to = amount is Lightning BTC received |
amount | required* | numeric | Amount in the currency implied by direction. Optional if toAddress (BOLT11) is provided — invoice sats win. |
toAddress | optional | string | BOLT11; when present, quote is derived from invoice amount |
Affiliate params (refcode, afftax) and FixedFloat extras (ccies, usd) are ignored / not implemented.
Example — direction to (real exercise quote)
// Request
{
"type": "fixed",
"fromCcy": "SOL",
"toCcy": "BTCLN",
"direction": "to",
"amount": "0.00004243"
}
// Response
{
"code": 0,
"msg": "OK",
"data": {
"from": {
"code": "SOL",
"network": "SOL",
"coin": "SOL",
"amount": 0.03815503,
"rate": 0.00116446,
"precision": 8,
"min": 0.025,
"max": 0.91611682,
"usd": 2.9,
"btc": 0.00004488
},
"to": {
"code": "BTCLN",
"network": "LN",
"coin": "BTC",
"amount": 0.00004243,
"rate": 858.76715387,
"precision": 8,
"min": 0.00002711,
"max": 0.00106478,
"usd": 2.74
},
"errors": []
}
}
data.errors values
| Code | Meaning |
|---|---|
OFFLINE_FROM / OFFLINE_TO | Pair or side unavailable (stale rates demote the pair) |
MAINTENANCE_FROM / MAINTENANCE_TO | Side in maintenance |
LIMIT_MIN / LIMIT_MAX | Amount outside min/max |
INVALID_FROM / INVALID_TO | Unknown or unsupported currency |
UNSUPPORTED_TYPE | Only fixed is allowed |
UNSUPPORTED_DIRECTION | Not from or to |
AMOUNT_MISMATCH | Provided amount ≠ BOLT11 amount (direction=to) |
MISSING_AMOUNT | No amount and no invoice |
POST /api/v2/create
Binding order create. Cost: 50 weight. Payout is always a Lightning invoice you supply — there is no on-chain BTC payout path.
typemust befixed(float orders are rejected).directionmust beto— amount is the BTC the invoice receives.toCcymust beBTCLN.toAddressmust be a valid BOLT11 with an amount. Ifamountis also sent, it must match the invoice’s BTC value.
Parameters
| Name | Type | Description | |
|---|---|---|---|
type | required | string | fixed |
fromCcy | required | string | Pay-in currency code |
toCcy | required | string | BTCLN |
direction | required | string | to |
amount | required* | numeric | BTC the invoice should receive; must match BOLT11 when both are sent |
toAddress | required | string | BOLT11 destination |
Idempotency (Lightning Swap extension)
Optionally send header Idempotency-Key: <opaque string> (or body field
idempotency_key). A repeated create with the same key on the same account
returns the original order instead of opening a second deposit.
Example — create (SOL → Lightning, from exercise)
// Request
{
"type": "fixed",
"fromCcy": "SOL",
"toCcy": "BTCLN",
"direction": "to",
"amount": "0.00004243",
"toAddress": "lnbc42430n1p49elex…cplvlg2n"
}
// Response (trimmed)
{
"code": 0,
"msg": "OK",
"data": {
"id": "QBSPEO",
"type": "fixed",
"email": "",
"status": "NEW",
"time": {
"reg": 1784479529,
"start": null,
"finish": null,
"update": 1784479529,
"expiration": 1784480129,
"left": 600
},
"from": {
"code": "SOL",
"coin": "SOL",
"network": "SOL",
"name": "Solana",
"alias": "solana",
"amount": "0.03815503",
"address": "DTJnkqp4Uxx4ccpzLqSnaRtoescQRgpm1s5Pra3LesXF",
"tag": "",
"tagName": null,
"tx": {
"id": null,
"amount": null,
"fee": null,
"ccyfee": null,
"timeReg": null,
"timeBlock": null,
"confirmations": null
},
"addressAlt": null,
"reqConfirmations": 1,
"maxConfirmations": 16
},
"to": {
"code": "BTCLN",
"coin": "BTC",
"network": "LN",
"name": "Bitcoin (Lightning)",
"alias": "lightning",
"amount": "0.00004243",
"address": "lnbc42430n1p49elex…cplvlg2n",
"tag": "",
"tagName": null,
"tx": { "id": null, "amount": null, "fee": null, "ccyfee": null,
"timeReg": null, "timeBlock": null, "confirmations": null }
},
"back": {
"code": "",
"amount": null,
"address": "",
"tx": { "id": null, "amount": null, "fee": null, "ccyfee": null,
"timeReg": null, "timeBlock": null, "confirmations": null }
},
"emergency": { "status": [], "choice": "NONE", "repeat": "0" },
"token": "ls7c2edffd2acd0be11bb1b6983975d7dc8d2945400a5b012b"
}
}
Persist data.id and data.token — both are required for
/order and /emergency. Show the payer
from.address, from.amount, and from.tag when present.
Deposit window is typically ~10 minutes (time.left at create).
Order object fields (create & order share this shape)
| Field | Type | Description |
|---|---|---|
data.id | string | 6-character order id |
data.token | string | Order access token (secret — treat like a credential) |
data.type | string | Always fixed here |
data.status | string | See statuses |
data.time.* | number | reg, start, finish, update, expiration, left |
data.from.* | object | Deposit side: code, amount, address, tag, confirmations, tx |
data.to.* | object | Payout side: BTCLN amount, bolt11 address, Lightning payment tx when paid |
data.back.* | object | Refund side when emergency / refunded |
data.emergency.* | object | status[], choice, repeat |
POST /api/v2/order
Fetch the latest order snapshot. Same data shape as create. Weight: 1.
Parameters
| Name | Type | Description | |
|---|---|---|---|
id | required | string | Order id from create |
token | required | string | data.token from create |
Example — completed order (poll after deposit + Lightning pay)
// Request
{ "id": "QBSPEO", "token": "ls7c2edffd2acd0be11bb1b6983975d7dc8d2945400a5b012b" }
// Response data (highlights)
{
"id": "QBSPEO",
"status": "DONE",
"time": {
"reg": 1784479529,
"start": 1784479622,
"finish": 1784479625,
"update": 1784479625,
"expiration": 1784480129,
"left": 502
},
"from": {
"code": "SOL",
"amount": "0.03815503",
"address": "DTJnkqp4Uxx4ccpzLqSnaRtoescQRgpm1s5Pra3LesXF",
"tx": {
"id": "5jFuRUiLFRK3LFdghA6BVx5KkT5b58uhSWqDqmu5wJrwngKNoq8by7i7DPi55Vuwq8GyNk7yBdctM2K4oBNnHdP8",
"amount": "0.03815503",
"fee": "0.00000635",
"ccyfee": "SOL",
"timeReg": 1784479622,
"timeBlock": 1784479622,
"confirmations": "12"
},
"reqConfirmations": 1,
"maxConfirmations": 16
},
"to": {
"code": "BTCLN",
"amount": "0.00004243",
"tx": {
"id": "dd57894b0f721cfa593857f69efd10ce920f0a7710f7deb73782004d19664ac1",
"amount": "0.00004243",
"fee": "0.00000011",
"ccyfee": "BTC",
"timeReg": 1784479625,
"timeBlock": 1784479625,
"confirmations": "1"
}
},
"emergency": { "status": [], "choice": "NONE", "repeat": "0" },
"token": "ls7c2edffd2acd0be11bb1b6983975d7dc8d2945400a5b012b"
}
When status=DONE after a successful swap, to.tx.id is the Lightning
payment preimage/hash identifier for the payout.
POST /api/v2/emergency
Choose an action when status is EMERGENCY.
Lightning Swap supports refund only —
choice=EXCHANGE is rejected because payout is a fixed BOLT11 amount and cannot be continued at a new market rate.
Weight: 1.
Parameters
| Name | Type | Description | |
|---|---|---|---|
id | required | string | Order id |
token | required | string | Order token |
choice | required | string | Must be REFUND (explicit — no default) |
address | required | string | Refund destination on the deposit network. Must differ from the deposit address. |
tag | optional | string | Memo / destination tag when the network requires it |
Response
Success returns a boolean data (FixedFloat-compatible) — not the order object.
Poll /order for refund progress (back.address, back.tx, then DONE).
// Request (classic underpay → refund)
{
"id": "WJ3IYG",
"token": "ls2e2ac023d09704e467cbbf3d620ea54f5a315c04582770eb",
"choice": "REFUND",
"address": "BfMe1deFYJwaSeD9XoN1X8xw1PtcYjginrbvkQjS9w9U"
}
// Response
{ "code": 0, "msg": "", "data": true }
Idempotent: repeating the same refund address after success returns success again.
A different address after submit returns 409.
Order statuses
| Status | Meaning |
|---|---|
NEW | Awaiting deposit |
PENDING | Deposit seen, waiting for confirmations |
EXCHANGE | Deposit confirmed; preparing payout |
WITHDRAW | Paying the Lightning invoice |
DONE | Terminal success — either Lightning paid, or refund broadcast completed |
EXPIRED | Deposit window ended without a qualifying deposit |
EMERGENCY | Needs /emergency (underpay, overpay, late deposit, …) |
Emergency reasons (data.emergency.status[])
| Value | Meaning |
|---|---|
LESS | Deposit amount below order amount (underpay) |
MORE | Deposit amount above order amount |
EXPIRED | Deposit arrived after expiration (within late-deposit grace) |
LIMIT | Amount outside limits; used together with LESS or MORE |
emergency.choice: NONE until the client calls /emergency, then
REFUND. EXCHANGE is never set by Lightning Swap.
Currency codes
Public codes match FixedFloat naming so pair strings port cleanly.
| Code | Asset | Network | Role | Req. confirmations |
|---|---|---|---|---|
SOL | SOL | Solana | Pay-in | 1 |
USDTSOL | USDT | Solana | Pay-in | 1 |
USDCSOL | USDC | Solana | Pay-in | 1 |
USDTTRC | USDT | Tron (TRC20) | Pay-in | 1 |
ETH | ETH | Ethereum | Pay-in | 4 |
USDT | USDT | Ethereum (ERC20) | Pay-in | 4 |
USDCETH | USDC | Ethereum (ERC20) | Pay-in | 4 |
BTCLN | BTC | Lightning | Payout only | — |
HTTP / code mapping
code | HTTP | Typical cause |
|---|---|---|
| 0 | 200 | Success (still check data.errors on price) |
| 400 | 400 | Validation / unsupported params / bad refund address |
| 401 | 401 | Missing/invalid key or signature |
| 403 | 403 | Account disabled |
| 404 | 404 | Unknown order or bad token |
| 409 | 409 | Conflict (e.g. refund already submitted to another address) |
| 429 | 429 | Weight budget or create gate |
| 503 | 503 | Pair offline, deposit address/watcher unavailable, refund in flight |
Worked example — happy path (SOL → BTCLN)
Captured from a full Lightning Swap exercise (sol_sol_lightningswap_exercise.json).
Sequence: rates → ccies → price → create → poll → DONE.
- Quote
direction=tofor0.00004243BTC → pay-in0.03815503SOL. - Create with matching BOLT11; receive deposit address
DTJnkqp4…, orderQBSPEO. - Payer sends exact
from.amount. - Poll until
DONE: deposit tx onfrom.tx, Lightning payment id onto.tx.
# Minimal create + poll loop (bash)
BODY=$(jq -nc --arg inv "$BOLT11" \
'{type:"fixed",fromCcy:"SOL",toCcy:"BTCLN",direction:"to",amount:"0.00004243",toAddress:$inv}')
SIGN=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$API_SECRET" | awk '{print $2}')
CREATE=$(curl -sS https://lightning-swap.com/api/v2/create \
-H "Content-Type: application/json; charset=UTF-8" \
-H "X-API-KEY: $API_KEY" -H "X-API-SIGN: $SIGN" -d "$BODY")
ID=$(jq -r .data.id <<<"$CREATE")
TOKEN=$(jq -r .data.token <<<"$CREATE")
# … instruct payer to send .data.from.amount to .data.from.address …
# then poll /api/v2/order with {"id","token"} until status is DONE / EMERGENCY / EXPIRED
Worked example — classic underpay → refund
From stress_underpay_classic_sol_sol_lightningswap.json:
order created for 0.038088 SOL → 0.00004265 BTC;
payer sent only 0.031544 SOL → EMERGENCY with LESS →
/emergency REFUND → DONE with refund tx on back.tx.
1. Order enters emergency after underpay
{
"code": 0,
"msg": "OK",
"data": {
"id": "WJ3IYG",
"type": "fixed",
"status": "EMERGENCY",
"from": {
"code": "SOL",
"amount": "0.038088",
"address": "7V3hvrvkZWdeFPAkDa2Z4im5FYfp1ajPpKgg5rTDWz8e",
"tx": {
"id": "25vM5nq7yixAszcsW3NJj4JBcFqNcP4mmXBfVpUGHTNWn8LSZX5zcEjJPpatXq7HHyUVZ9tQ4Yz4pyPaCVgwEPfH",
"amount": "0.031544",
"fee": "0.00000635",
"ccyfee": "SOL",
"confirmations": "4"
}
},
"to": {
"code": "BTCLN",
"amount": "0.00004265",
"tx": { "id": null, "amount": null }
},
"back": {
"code": "SOL",
"amount": "0.031544000",
"address": "",
"tx": { "id": null }
},
"emergency": {
"status": ["LESS"],
"choice": "NONE",
"repeat": "0"
},
"token": "ls2e2ac023d09704e467cbbf3d620ea54f5a315c04582770eb"
}
}
2. Submit refund
{
"id": "WJ3IYG",
"token": "ls2e2ac023d09704e467cbbf3d620ea54f5a315c04582770eb",
"choice": "REFUND",
"address": "BfMe1deFYJwaSeD9XoN1X8xw1PtcYjginrbvkQjS9w9U"
}
→ { "code": 0, "msg": "", "data": true }
3. Terminal state after refund broadcast
{
"status": "DONE",
"from": {
"tx": {
"id": "25vM5nq7yixAszcsW3NJj4JBcFqNcP4mmXBfVpUGHTNWn8LSZX5zcEjJPpatXq7HHyUVZ9tQ4Yz4pyPaCVgwEPfH",
"amount": "0.031544"
}
},
"to": { "tx": { "id": null } },
"back": {
"code": "SOL",
"amount": "0.031539000",
"address": "BfMe1deFYJwaSeD9XoN1X8xw1PtcYjginrbvkQjS9w9U",
"tx": {
"id": "LFdgqWhtpugZbCeQjm876Quva5bYKvtoHSsotZjPh7SrpgnEkf6mR2kjCxGmdCUvfsQdjBknd1VGxpUj4j3FWad",
"amount": "0.031539000",
"fee": "0.000005",
"ccyfee": "SOL"
}
},
"emergency": {
"status": ["LESS"],
"choice": "REFUND",
"repeat": "0"
}
}
Note back.amount matches the credited deposit (full refund). Native SOL/ETH
still lose on-chain gas. to.tx stays empty because the Lightning invoice was
never paid.
Not supported (FixedFloat surfaces we omit)
Compared to the full FixedFloat API, Lightning Swap does not implement:
| FixedFloat surface | Lightning Swap |
|---|---|
GET /rates/float.xml / type=float | Not available — fixed rate only |
Arbitrary toCcy (on-chain payouts) | Payout is always BTCLN |
direction=from on /create | Create requires direction=to (price still supports both) |
POST /api/v2/setEmail | Not implemented |
POST /api/v2/qr | Not implemented — build QR client-side from from.address |
choice=EXCHANGE on emergency | Rejected — use REFUND |
Affiliate refcode / afftax | Ignored |
| Official PHP/Python FixedFloat client libraries | Use the signing examples above against our base URL |
Need keys? Generate an API token. Questions about a production integration — use the support channel on your account. Reference behaviour for clients that already speak FixedFloat: ff.io/api.