Documentation Index Fetch the complete documentation index at: https://grid.lightspark.com/llms.txt
Use this file to discover all available pages before exploring further.
Overview
Grid enables seamless conversion between fiat currencies and cryptocurrencies via the Lightning Network. Use quotes to lock exchange rates and get payment instructions for completing transfers.
On-ramp (Fiat → Crypto): User sends fiat → Grid detects payment → Crypto sent to wallet
Off-ramp (Crypto → Fiat): Execute quote → Grid processes crypto → Fiat sent to bank
Prerequisites
Customer created in Grid
On-ramps: Destination crypto wallet (Spark address) + webhook endpoint
Off-ramps: Internal account with crypto + external bank account registered
On-ramp: Fiat to crypto
Create a quote
First, create an external account for the crypto destination:
curl -X POST 'https://api.lightspark.com/grid/2025-10-13/customers/external-accounts' \
-H 'Authorization: Basic $GRID_CLIENT_ID:$GRID_CLIENT_SECRET' \
-H 'Content-Type: application/json' \
-d '{
"customerId": "Customer:019542f5-b3e7-1d02-0000-000000000001",
"currency": "BTC",
"accountInfo": {
"accountType": "SPARK_WALLET",
"address": "spark1pgssyuuuhnrrdjswal5c3s3rafw9w3y5dd4cjy3duxlf7hjzkp0rqx6dj6mrhu"
}
}'
Then create a quote using the external account ID:
curl -X POST 'https://api.lightspark.com/grid/2025-10-13/quotes' \
-H 'Authorization: Basic $GRID_CLIENT_ID:$GRID_CLIENT_SECRET' \
-H 'Content-Type: application/json' \
-d '{
"source": {
"customerId": "Customer:019542f5-b3e7-1d02-0000-000000000001",
"currency": "USD"
},
"destination": {
"destinationType": "ACCOUNT",
"accountId": "ExternalAccount:b23dcbd6-dced-4ec4-b756-3c3a9ea3d456"
},
"lockedCurrencySide": "RECEIVING",
"lockedCurrencyAmount": 100000,
"description": "Buy 0.001 BTC"
}'
Response includes payment instructions:
{
"id" : "Quote:019542f5-b3e7-1d02-0000-000000000006" ,
"totalSendingAmount" : 6500 ,
"receivingAmount" : 100000 ,
"expiresAt" : "2025-10-03T12:05:00Z" ,
"paymentInstructions" : [
{
"accountOrWalletInfo" : {
"accountType" : "US_ACCOUNT" ,
"reference" : "UMA-Q12345-REF" ,
"accountNumber" : "1234567890" ,
"routingNumber" : "021000021" ,
"bankName" : "Grid Settlement Bank"
}
},
{
"accountOrWalletInfo" : {
"accountType" : "SOLANA_WALLET" ,
"assetType" : "USDC" ,
"address" : "4Nd1m6Qkq7RfKuE5vQ9qP9Tn6H94Ueqb4xXHzsAbd8Wg"
}
}
]
}
Display payment instructions
function displayPaymentInstructions ( quote ) {
const instructions = quote . paymentInstructions [ 0 ];
return {
amount: `$ ${ ( quote . totalSendingAmount / 100 ). toFixed ( 2 ) } ` ,
bankName: instructions . accountOrWalletInfo . bankName ,
accountNumber: instructions . accountOrWalletInfo . accountNumber ,
routingNumber: instructions . accountOrWalletInfo . routingNumber ,
referenceCode: instructions . reference , // User must include this
expiresAt: quote . expiresAt ,
willReceive: ` ${ quote . receivingAmount / 100000000 } BTC` ,
};
}
Monitor completion
Grid sends OUTGOING_PAYMENT.<STATUS> webhooks as the transaction progresses:
app . post ( "/webhooks/grid" , async ( req , res ) => {
const { type , data } = req . body ;
if ( type === "OUTGOING_PAYMENT.COMPLETED" ) {
await notifyUser ( data . customerId , {
message: "Your Bitcoin purchase is complete!" ,
amount: ` ${ data . receivedAmount . amount / 100000000 } BTC` ,
});
} else if ( type === "OUTGOING_PAYMENT.FAILED" ) {
await notifyUser ( data . customerId , {
message: `Purchase failed: ${ data . failureReason } ` ,
});
// Refund webhook (OUTGOING_PAYMENT.REFUND_*) will follow
}
res . status ( 200 ). json ({ received: true });
});
See the Transaction Lifecycle guide for all status transitions and refund handling.
Off-ramp: Crypto to fiat
Create and execute a quote
// 1. Create quote
const quote = await fetch ( "https://api.lightspark.com/grid/2025-10-13/quotes" , {
method: "POST" ,
body: JSON . stringify ({
source: {
accountId: "InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123" ,
},
destination: {
accountId: "ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965" ,
currency: "USD" ,
},
lockedCurrencySide: "SENDING" ,
lockedCurrencyAmount: 100000 , // 0.001 BTC
description: "Sell 0.001 BTC" ,
}),
}). then (( r ) => r . json ());
// 2. Execute quote
const result = await fetch (
`https://api.lightspark.com/grid/2025-10-13/quotes/ ${ quote . id } /execute` ,
{
method: "POST" ,
headers: { Authorization: `Basic ${ credentials } ` },
}
). then (( r ) => r . json ());
Track completion
app . post ( "/webhooks/grid" , async ( req , res ) => {
const { type , data } = req . body ;
if ( type === "OUTGOING_PAYMENT.COMPLETED" ) {
await notifyUser ( data . customerId , {
message: "Your USD withdrawal is complete!" ,
amount: `$ ${ data . receivedAmount . amount / 100 } ` ,
});
} else if ( type === "OUTGOING_PAYMENT.FAILED" ) {
await notifyUser ( data . customerId , {
message: `Withdrawal failed: ${ data . failureReason } ` ,
});
}
res . status ( 200 ). json ({ received: true });
});
For instant on-ramps (e.g., reward payouts), use immediatelyExecute: true with an internal account source:
curl -X POST 'https://api.lightspark.com/grid/2025-10-13/quotes' \
-H 'Authorization: Basic $GRID_CLIENT_ID:$GRID_CLIENT_SECRET' \
-H 'Content-Type: application/json' \
-d '{
"source": {
"sourceType": "ACCOUNT",
"accountId": "InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123"
},
"destination": {
"destinationType": "ACCOUNT",
"accountId": "ExternalAccount:b23dcbd6-dced-4ec4-b756-3c3a9ea3d456"
},
"lockedCurrencySide": "RECEIVING",
"lockedCurrencyAmount": 100000,
"immediatelyExecute": true
}'
immediatelyExecute can only be used with sources that are either internal accounts or external accounts with direct pull functionality (e.g., ACH pull).
Best practices
async function refreshQuoteIfNeeded ( quote ) {
const expiresAt = new Date ( quote . expiresAt );
const now = new Date ();
if ( expiresAt - now < 60000 ) {
// Less than 1 minute left
return await createNewQuote ( quote . originalParams );
}
return quote ;
}
Settlement times by payment rail
const settlementTimes = {
US_ACCOUNT: "1-3 business days (ACH)" ,
WIRE: "Same day" ,
SPARK_WALLET: "Instant" ,
PIX: "Instant" ,
SEPA: "1-2 business days" ,
};
Handle failed transactions
if ( transaction . status === "FAILED" ) {
await notifyUser ( transaction . customerId , {
message: "Transaction failed" ,
reason: transaction . failureReason ,
action: "retry" ,
});
if ( transaction . failureReason === "QUOTE_EXPIRED" ) {
await createNewQuote ( transaction . originalParams );
}
}
Next steps