curl --request POST \
--url https://api.paj.cash/pub/v2/offramp \
--header 'Content-Type: application/json' \
--header 'x-api-key: <api-key>' \
--data '
{
"accountNumber": "679be2527f99f556d77b8cc2",
"currency": "NGN",
"bankCode": "000016",
"mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"amount": 100,
"fiatAmount": 100,
"chain": "SOLANA",
"webhookURL": "https://mydomain.com/webhook",
"saveBeneficiary": true,
"description": "Payment for order #123",
"businessUSDCFee": 0.5,
"bvn": "22222222222"
}
'import requests
url = "https://api.paj.cash/pub/v2/offramp"
payload = {
"accountNumber": "679be2527f99f556d77b8cc2",
"currency": "NGN",
"bankCode": "000016",
"mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"amount": 100,
"fiatAmount": 100,
"chain": "SOLANA",
"webhookURL": "https://mydomain.com/webhook",
"saveBeneficiary": True,
"description": "Payment for order #123",
"businessUSDCFee": 0.5,
"bvn": "22222222222"
}
headers = {
"x-api-key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'x-api-key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
accountNumber: '679be2527f99f556d77b8cc2',
currency: 'NGN',
bankCode: '000016',
mint: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
amount: 100,
fiatAmount: 100,
chain: 'SOLANA',
webhookURL: 'https://mydomain.com/webhook',
saveBeneficiary: true,
description: 'Payment for order #123',
businessUSDCFee: 0.5,
bvn: '22222222222'
})
};
fetch('https://api.paj.cash/pub/v2/offramp', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.paj.cash/pub/v2/offramp",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'accountNumber' => '679be2527f99f556d77b8cc2',
'currency' => 'NGN',
'bankCode' => '000016',
'mint' => 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
'amount' => 100,
'fiatAmount' => 100,
'chain' => 'SOLANA',
'webhookURL' => 'https://mydomain.com/webhook',
'saveBeneficiary' => true,
'description' => 'Payment for order #123',
'businessUSDCFee' => 0.5,
'bvn' => '22222222222'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"x-api-key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.paj.cash/pub/v2/offramp"
payload := strings.NewReader("{\n \"accountNumber\": \"679be2527f99f556d77b8cc2\",\n \"currency\": \"NGN\",\n \"bankCode\": \"000016\",\n \"mint\": \"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v\",\n \"amount\": 100,\n \"fiatAmount\": 100,\n \"chain\": \"SOLANA\",\n \"webhookURL\": \"https://mydomain.com/webhook\",\n \"saveBeneficiary\": true,\n \"description\": \"Payment for order #123\",\n \"businessUSDCFee\": 0.5,\n \"bvn\": \"22222222222\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("x-api-key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.paj.cash/pub/v2/offramp")
.header("x-api-key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"accountNumber\": \"679be2527f99f556d77b8cc2\",\n \"currency\": \"NGN\",\n \"bankCode\": \"000016\",\n \"mint\": \"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v\",\n \"amount\": 100,\n \"fiatAmount\": 100,\n \"chain\": \"SOLANA\",\n \"webhookURL\": \"https://mydomain.com/webhook\",\n \"saveBeneficiary\": true,\n \"description\": \"Payment for order #123\",\n \"businessUSDCFee\": 0.5,\n \"bvn\": \"22222222222\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.paj.cash/pub/v2/offramp")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["x-api-key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"accountNumber\": \"679be2527f99f556d77b8cc2\",\n \"currency\": \"NGN\",\n \"bankCode\": \"000016\",\n \"mint\": \"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v\",\n \"amount\": 100,\n \"fiatAmount\": 100,\n \"chain\": \"SOLANA\",\n \"webhookURL\": \"https://mydomain.com/webhook\",\n \"saveBeneficiary\": true,\n \"description\": \"Payment for order #123\",\n \"businessUSDCFee\": 0.5,\n \"bvn\": \"22222222222\"\n}"
response = http.request(request)
puts response.read_body{
"id": "679be2527f99f556d77b8cc2",
"address": "4KSLE7EU1P7PQ8Rc4hdb2ZKq2JmWHD8UXJp7guEdyT9j",
"signature": "2oLx7XrXP7Df9dZca1difJ91ZFmA44EEadNqrZTecta6ugcX8BAETbpvKQs54r58fmJTQVLzUSuzvRBFwS1GfJLo",
"mint": "So11111111111111111111111111111111111111112",
"currency": "NGN",
"amount": 100000,
"usdcAmount": 100,
"fiatAmount": 100,
"sender": "4KSLE7EU1P7PQ8Rc4hdb2ZKq2JmWHD8UXJp7guEdyT9j",
"recipient": "4KSLE7EU1P7PQ8Rc4hdb2ZKq2JmWHD8UXJp7guEdyT9j",
"rate": 9,
"status": "PENDDING",
"transactionType": "OFF_RAMP",
"createdAt": "2026-09-28T14:55:27.097Z",
"accountNumber": "0025635480",
"accountName": "Paj Inc.",
"bank": "First Bank",
"fee": 0.2,
"chain": "SOLANA"
}Create an offramp order
Opens an offramp order that pays out to a bank account identified by bank code and account number. The account is confirmed with the provider first, so an unknown bank code or an account number that cannot be resolved is rejected before any order exists. When a bvn is given the order is credited to that end user — traced through their KYC record, or verified and registered if the BVN is new; without one the order is not attributed to any user. Provide either amount (token) or fiatAmount, not both. The returned address is where the tokens should be sent to fund the order.
curl --request POST \
--url https://api.paj.cash/pub/v2/offramp \
--header 'Content-Type: application/json' \
--header 'x-api-key: <api-key>' \
--data '
{
"accountNumber": "679be2527f99f556d77b8cc2",
"currency": "NGN",
"bankCode": "000016",
"mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"amount": 100,
"fiatAmount": 100,
"chain": "SOLANA",
"webhookURL": "https://mydomain.com/webhook",
"saveBeneficiary": true,
"description": "Payment for order #123",
"businessUSDCFee": 0.5,
"bvn": "22222222222"
}
'import requests
url = "https://api.paj.cash/pub/v2/offramp"
payload = {
"accountNumber": "679be2527f99f556d77b8cc2",
"currency": "NGN",
"bankCode": "000016",
"mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"amount": 100,
"fiatAmount": 100,
"chain": "SOLANA",
"webhookURL": "https://mydomain.com/webhook",
"saveBeneficiary": True,
"description": "Payment for order #123",
"businessUSDCFee": 0.5,
"bvn": "22222222222"
}
headers = {
"x-api-key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'x-api-key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
accountNumber: '679be2527f99f556d77b8cc2',
currency: 'NGN',
bankCode: '000016',
mint: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
amount: 100,
fiatAmount: 100,
chain: 'SOLANA',
webhookURL: 'https://mydomain.com/webhook',
saveBeneficiary: true,
description: 'Payment for order #123',
businessUSDCFee: 0.5,
bvn: '22222222222'
})
};
fetch('https://api.paj.cash/pub/v2/offramp', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.paj.cash/pub/v2/offramp",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'accountNumber' => '679be2527f99f556d77b8cc2',
'currency' => 'NGN',
'bankCode' => '000016',
'mint' => 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
'amount' => 100,
'fiatAmount' => 100,
'chain' => 'SOLANA',
'webhookURL' => 'https://mydomain.com/webhook',
'saveBeneficiary' => true,
'description' => 'Payment for order #123',
'businessUSDCFee' => 0.5,
'bvn' => '22222222222'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"x-api-key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.paj.cash/pub/v2/offramp"
payload := strings.NewReader("{\n \"accountNumber\": \"679be2527f99f556d77b8cc2\",\n \"currency\": \"NGN\",\n \"bankCode\": \"000016\",\n \"mint\": \"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v\",\n \"amount\": 100,\n \"fiatAmount\": 100,\n \"chain\": \"SOLANA\",\n \"webhookURL\": \"https://mydomain.com/webhook\",\n \"saveBeneficiary\": true,\n \"description\": \"Payment for order #123\",\n \"businessUSDCFee\": 0.5,\n \"bvn\": \"22222222222\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("x-api-key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.paj.cash/pub/v2/offramp")
.header("x-api-key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"accountNumber\": \"679be2527f99f556d77b8cc2\",\n \"currency\": \"NGN\",\n \"bankCode\": \"000016\",\n \"mint\": \"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v\",\n \"amount\": 100,\n \"fiatAmount\": 100,\n \"chain\": \"SOLANA\",\n \"webhookURL\": \"https://mydomain.com/webhook\",\n \"saveBeneficiary\": true,\n \"description\": \"Payment for order #123\",\n \"businessUSDCFee\": 0.5,\n \"bvn\": \"22222222222\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.paj.cash/pub/v2/offramp")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["x-api-key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"accountNumber\": \"679be2527f99f556d77b8cc2\",\n \"currency\": \"NGN\",\n \"bankCode\": \"000016\",\n \"mint\": \"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v\",\n \"amount\": 100,\n \"fiatAmount\": 100,\n \"chain\": \"SOLANA\",\n \"webhookURL\": \"https://mydomain.com/webhook\",\n \"saveBeneficiary\": true,\n \"description\": \"Payment for order #123\",\n \"businessUSDCFee\": 0.5,\n \"bvn\": \"22222222222\"\n}"
response = http.request(request)
puts response.read_body{
"id": "679be2527f99f556d77b8cc2",
"address": "4KSLE7EU1P7PQ8Rc4hdb2ZKq2JmWHD8UXJp7guEdyT9j",
"signature": "2oLx7XrXP7Df9dZca1difJ91ZFmA44EEadNqrZTecta6ugcX8BAETbpvKQs54r58fmJTQVLzUSuzvRBFwS1GfJLo",
"mint": "So11111111111111111111111111111111111111112",
"currency": "NGN",
"amount": 100000,
"usdcAmount": 100,
"fiatAmount": 100,
"sender": "4KSLE7EU1P7PQ8Rc4hdb2ZKq2JmWHD8UXJp7guEdyT9j",
"recipient": "4KSLE7EU1P7PQ8Rc4hdb2ZKq2JmWHD8UXJp7guEdyT9j",
"rate": 9,
"status": "PENDDING",
"transactionType": "OFF_RAMP",
"createdAt": "2026-09-28T14:55:27.097Z",
"accountNumber": "0025635480",
"accountName": "Paj Inc.",
"bank": "First Bank",
"fee": 0.2,
"chain": "SOLANA"
}Naming the destination
The bank account is given as abankCode (from Get
banks) plus an accountNumber. Before any order is
written, Paj confirms that pair with the payout rail. An account number the bank
cannot resolve is a 400 and leaves nothing behind — there is no half-created
order to clean up.
The confirmed account holder’s name comes back on the response as accountName.
It is the bank’s answer, not an echo of your input, so it is worth showing to
the user as a last check before they part with funds.
Amount: token or fiat, never both
Send exactly one of:amount— the quantity ofmintyou are selling. Paj prices it and tells you thefiatAmountthat will land.fiatAmount— the payout you want the recipient to receive. Paj works backwards and tells you theamountofmintto send.
400; so is sending neither. mint is required either way,
and chain defaults to SOLANA.
Choosing the chain
Orders open on any supported chain:SOLANA, TON,
ETHEREUM, BASE, BSC, MONAD or ARC. This is the only way to pay out
from a chain other than Solana — the bank account
address is Solana only.
mint is the token’s address on that chain: a Solana mint, an ERC-20 contract,
or a TON jetton master ("TON" for Toncoin). It is checked against chain, so
an EVM contract sent without a chain — which defaults to SOLANA — is a
400. The chain’s settlement stablecoin —
USDC, or USDT on TON — pays out directly; anything else is priced at its USD
value when it lands.
{
"bankCode": "000016",
"accountNumber": "0025635480",
"currency": "NGN",
"amount": 20,
"chain": "BASE",
"mint": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
}
address that comes back is in that chain’s format, and is watched on that
chain alone. On the EVM chains the address is valid on all five networks, so a
user sending from a multi-chain wallet can easily pick the wrong one — make the
chain as prominent as the address when you show it.
Attributing the order to a user
bvn is optional and identifies the end user the order belongs to. When you
send one, Paj traces it to that user through their KYC record; if the BVN is new
to Paj, it is verified with the identity provider and the user is registered from
the verified bio before the order is created. A BVN that fails verification is a
400.
Omit it and the order is simply not attributed to any individual — it stays a
business-level order under your API key. The payout still works, but it goes out
without a payer name attached.
Funding the order
The responseaddress is where the tokens go. Send amount of mint on
chain to that address and the order settles on its own — the deposit is the
instruction, and there is no second call to confirm it. Track progress by
status, which starts at INIT and ends at COMPLETED or ERROR.
address from the response you are acting on — never to one you cached from a
previous order.Getting told when it settles
PajPOSTs the order back to you on every status change, in the same shape as
this endpoint’s response. It goes to the order’s webhookURL if you set one,
and otherwise to the rampWebhookURL configured on your API key — so a key set
up once with Update your webhook URLs covers
every order without repeating the URL. With neither, you are polling.
Deliveries are signed; see Webhook signatures.Authorizations
Body
Account Number to receive funds
"679be2527f99f556d77b8cc2"
Currency to receive funds
"NGN"
Code of the bank that receives the payout
"000016"
Mint address of the token being sold, on the given chain. On TON, a jetton master address or "TON" for Toncoin.
"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
Token amount (provide either amount or fiatAmount, not both)
100
Fiat amount (provide either amount or fiatAmount, not both)
100
Chain
"SOLANA"
Callback webhook url
"https://mydomain.com/webhook"
Save beneficiary
true
Payment description
"Payment for order #123"
Business fee
0.5
BVN of the end user the order is created for. It is traced to that user's KYC record; if the BVN is not on file it is verified and the user is registered before the order is opened.
"22222222222"
Response
The created offramp order
Transaction Id
"679be2527f99f556d77b8cc2"
Address
"4KSLE7EU1P7PQ8Rc4hdb2ZKq2JmWHD8UXJp7guEdyT9j"
Transaction Signature
"2oLx7XrXP7Df9dZca1difJ91ZFmA44EEadNqrZTecta6ugcX8BAETbpvKQs54r58fmJTQVLzUSuzvRBFwS1GfJLo"
Mint Address
"So11111111111111111111111111111111111111112"
Currency
"NGN"
Mint Amount
100000
Usdc Amount
100
Fiat Amount
100
Wallet Public Key
"4KSLE7EU1P7PQ8Rc4hdb2ZKq2JmWHD8UXJp7guEdyT9j"
Recipient Public Key
"4KSLE7EU1P7PQ8Rc4hdb2ZKq2JmWHD8UXJp7guEdyT9j"
Rate of currency to usdc
9
Transaction Status
"PENDDING"
Transaction Type
"OFF_RAMP"
Transaction creation date
"2026-09-28T14:55:27.097Z"
Account Number (for onramp transactions)
"0025635480"
Account Name (for onramp transactions)
"Paj Inc."
Bank Name (for onramp transactions)
"First Bank"
Transaction fee in usdc
0.2
Chain
"SOLANA"