FX & Currency Conversion
Get live exchange rates, create quotes, and execute currency conversions.
Get Live FX Rates
GET /fx/rates
Returns all supported currency pairs with current buy and sell rates.
Headers
| Field | Value |
|---|---|
| Authorization | Bearer PAYSCRIBE_API_KEY |
Request
- cURL
- Node.js
- Python
- Go
curl -X GET "https://sandbox.payscribe.ng/api/v1/fx/rates" \
-H "Authorization: Bearer $PAYSCRIBE_API_KEY"
const response = await fetch('https://sandbox.payscribe.ng/api/v1/fx/rates', {
method: 'GET',
headers: {Authorization: `Bearer ${process.env.PAYSCRIBE_API_KEY}`},
});
const result = await response.json().catch(() => null);
if (!response.ok) throw new Error(`Payscribe request failed: ${response.status}`);
console.log(result);
import os
import requests
response = requests.get(
'https://sandbox.payscribe.ng/api/v1/fx/rates',
headers={'Authorization': f"Bearer {os.environ['PAYSCRIBE_API_KEY']}"},
timeout=20,
)
response.raise_for_status()
print(response.json())
package main
import (
"fmt"
"io"
"net/http"
"os"
)
func main() {
var body io.Reader
request, err := http.NewRequest(http.MethodGet, "https://sandbox.payscribe.ng/api/v1/fx/rates", body)
if err != nil { panic(err) }
request.Header.Set("Authorization", "Bearer "+os.Getenv("PAYSCRIBE_API_KEY"))
response, err := http.DefaultClient.Do(request)
if err != nil { panic(err) }
defer response.Body.Close()
responseBody, _ := io.ReadAll(response.Body)
if response.StatusCode < 200 || response.StatusCode > 299 { panic(fmt.Sprintf("Payscribe request failed: %s", response.Status)) }
fmt.Println(string(responseBody))
}
Response
Status: 200 OK
{
"status": true,
"description": "Rates board fetched successfully.",
"message": {
"details": {
"rates": [
{
"base": "USD",
"quote": "NGN",
"rate": 1535.5,
"buy": 1520.5,
"sell": 1550.5,
"pair": "USD/NGN",
"source": "sandbox",
"fetched_at": "2026-07-20T12:00:00Z",
"age": 0,
"is_stale": false,
"spread_bps": 100
},
{
"base": "USDT",
"quote": "NGN",
"rate": 1535.5,
"buy": 1520.5,
"sell": 1550.5,
"pair": "USDT/NGN",
"source": "sandbox",
"fetched_at": "2026-07-20T12:00:00Z",
"age": 0,
"is_stale": false,
"spread_bps": 100
},
{
"base": "USDC",
"quote": "NGN",
"rate": 1535.5,
"buy": 1520.5,
"sell": 1550.5,
"pair": "USDC/NGN",
"source": "sandbox",
"fetched_at": "2026-07-20T12:00:00Z",
"age": 0,
"is_stale": false,
"spread_bps": 100
},
{
"base": "NGN",
"quote": "USD",
"rate": 0.00065,
"buy": 0.00065,
"sell": 0.00065,
"pair": "NGN/USD",
"source": "sandbox",
"fetched_at": "2026-07-20T12:00:00Z",
"age": 0,
"is_stale": false,
"spread_bps": 100
}
],
"quota": {
"calls_today": 1,
"free_quota": 20,
"remaining": 19,
"charged": false,
"fee_usd": 0.0
}
}
},
"status_code": 200
}
Only these four pairs exist today — USD/NGN, USDT/NGN, USDC/NGN, and NGN/USD. EUR and GBP pairs are not supported by any environment.
Get Rate for Two Currencies
GET /fx/rate
Get the current exchange rate between two specific currencies.
Headers
| Field | Value |
|---|---|
| Authorization | Bearer PAYSCRIBE_API_KEY |
Query Parameters
| Field | Type | Required | Description |
|---|---|---|---|
from | string | Yes | Source currency code (e.g., USD). |
to | string | Yes | Target currency code (e.g., NGN). |
Request
- cURL
- Node.js
- Python
- Go
curl -X GET "https://sandbox.payscribe.ng/api/v1/fx/rate?from=USD&to=NGN" \
-H "Authorization: Bearer $PAYSCRIBE_API_KEY"
const response = await fetch('https://sandbox.payscribe.ng/api/v1/fx/rate?from=USD&to=NGN', {
method: 'GET',
headers: {Authorization: `Bearer ${process.env.PAYSCRIBE_API_KEY}`},
});
const result = await response.json().catch(() => null);
if (!response.ok) throw new Error(`Payscribe request failed: ${response.status}`);
console.log(result);
import os
import requests
response = requests.get(
'https://sandbox.payscribe.ng/api/v1/fx/rate?from=USD&to=NGN',
headers={'Authorization': f"Bearer {os.environ['PAYSCRIBE_API_KEY']}"},
timeout=20,
)
response.raise_for_status()
print(response.json())
package main
import (
"fmt"
"io"
"net/http"
"os"
)
func main() {
var body io.Reader
request, err := http.NewRequest(http.MethodGet, "https://sandbox.payscribe.ng/api/v1/fx/rate?from=USD&to=NGN", body)
if err != nil { panic(err) }
request.Header.Set("Authorization", "Bearer "+os.Getenv("PAYSCRIBE_API_KEY"))
response, err := http.DefaultClient.Do(request)
if err != nil { panic(err) }
defer response.Body.Close()
responseBody, _ := io.ReadAll(response.Body)
if response.StatusCode < 200 || response.StatusCode > 299 { panic(fmt.Sprintf("Payscribe request failed: %s", response.Status)) }
fmt.Println(string(responseBody))
}
Response
Status: 200 OK
{
"status": true,
"description": "Rate lookup successful.!",
"message": {
"details": {
"rate": 1520.5,
"currency_from": "USD",
"currency_to": "NGN",
"rate_token": "a1b2c3d4e5f6...",
"created_at": "2026-07-20 13:00:00",
"expiry_at": "2026-07-20 13:10:00"
}
},
"status_code": 200
}
rate_token is not consumed by any other FX endpoint today — it does not need to be passed to Create Quote. Note created_at is computed roughly an hour ahead of the actual request time server-side; treat both timestamps as indicative, not literal.
Create Quote
POST /currency-pair
Generate a firm quote for a currency conversion. Quotes are valid for a limited time.
Headers
| Field | Value |
|---|---|
| Authorization | Bearer PAYSCRIBE_API_KEY |
| Content-Type | application/json |
Body Parameters
| Field | Type | Required | Description |
|---|---|---|---|
from | string | Yes | Source currency code (e.g., USD). |
to | string | Yes | Target currency code (e.g., NGN). |
amount | number | Yes | Amount to convert in the source currency. |
Request
- cURL
- Node.js
- Python
- Go
curl -X POST "https://sandbox.payscribe.ng/api/v1/currency-pair" \
-H "Authorization: Bearer $PAYSCRIBE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"from": "USD",
"to": "NGN",
"amount": 100
}'
const response = await fetch('https://sandbox.payscribe.ng/api/v1/currency-pair', {
method: 'POST',
headers: {Authorization: `Bearer ${process.env.PAYSCRIBE_API_KEY}`, 'Content-Type': 'application/json'},
body: JSON.stringify({
"from": "USD",
"to": "NGN",
"amount": 100
}),
});
const result = await response.json().catch(() => null);
if (!response.ok) throw new Error(`Payscribe request failed: ${response.status}`);
console.log(result);
import os
import requests
import json
payload = json.loads(r'''{
"from": "USD",
"to": "NGN",
"amount": 100
}''')
response = requests.post(
'https://sandbox.payscribe.ng/api/v1/currency-pair',
headers={'Authorization': f"Bearer {os.environ['PAYSCRIBE_API_KEY']}", 'Content-Type': 'application/json'},
json=payload,
timeout=20,
)
response.raise_for_status()
print(response.json())
package main
import (
"strings"
"fmt"
"io"
"net/http"
"os"
)
func main() {
body := strings.NewReader(`{
"from": "USD",
"to": "NGN",
"amount": 100
}`)
request, err := http.NewRequest(http.MethodPost, "https://sandbox.payscribe.ng/api/v1/currency-pair", body)
if err != nil { panic(err) }
request.Header.Set("Authorization", "Bearer "+os.Getenv("PAYSCRIBE_API_KEY"))
request.Header.Set("Content-Type", "application/json")
response, err := http.DefaultClient.Do(request)
if err != nil { panic(err) }
defer response.Body.Close()
responseBody, _ := io.ReadAll(response.Body)
if response.StatusCode < 200 || response.StatusCode > 299 { panic(fmt.Sprintf("Payscribe request failed: %s", response.Status)) }
fmt.Println(string(responseBody))
}
Response
Status: 200 OK
{
"status": true,
"description": "Quote created successfully.",
"message": {
"details": {
"quote_id": "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0",
"from": "USD",
"to": "NGN",
"amount": 100,
"rate": 1535.5,
"ngn_amount": 153550,
"expires_at": "2026-07-20 11:56:00"
}
},
"status_code": 200
}
There is no fee field, and quote_id is not signed — no sig is returned. The quote also expires in 60 seconds, much sooner than the timestamp above suggests; execute it immediately.
Execute Quote
POST /execute-quote
Convert currency at a previously quoted rate. The quote must not have expired.
Tracing the routed method name (Endpoints::executeQuote() dispatches to a method named create_fx) shows create_fx is defined only on the live processor (App\Libraries\ApiEndpoints::create_fx()) — the sandbox processor class (App\Libraries\SandboxApiEndpoints) has no method by that name and does not extend the live class. Every sandbox call to this endpoint is caught as an unhandled engine error and returned as a generic 500 ("A technical error occurred."), regardless of the request body. There is also no way to obtain the sig value below — Create Quote's real response has no sig field. This needs an engineering fix, not a documentation fix.
Headers
| Field | Value |
|---|---|
| Authorization | Bearer PAYSCRIBE_API_KEY |
| Content-Type | application/json |
Body Parameters
| Field | Type | Required | Description |
|---|---|---|---|
quote_id | string | Yes | The quote ID returned from Create Quote. |
sig | string | Yes | Not currently produced by Create Quote — see above. |
Request
- cURL
- Node.js
- Python
- Go
curl -X POST "https://sandbox.payscribe.ng/api/v1/execute-quote" \
-H "Authorization: Bearer $PAYSCRIBE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"quote_id": "qt_abc123", "sig": "abc123...signature..."}'
const response = await fetch('https://sandbox.payscribe.ng/api/v1/execute-quote', {
method: 'POST',
headers: {Authorization: `Bearer ${process.env.PAYSCRIBE_API_KEY}`, 'Content-Type': 'application/json'},
body: JSON.stringify({
"quote_id": "qt_abc123",
"sig": "abc123...signature..."
}),
});
const result = await response.json().catch(() => null);
if (!response.ok) throw new Error(`Payscribe request failed: ${response.status}`);
console.log(result);
import os
import requests
import json
payload = json.loads(r'''{
"quote_id": "qt_abc123",
"sig": "abc123...signature..."
}''')
response = requests.post(
'https://sandbox.payscribe.ng/api/v1/execute-quote',
headers={'Authorization': f"Bearer {os.environ['PAYSCRIBE_API_KEY']}", 'Content-Type': 'application/json'},
json=payload,
timeout=20,
)
response.raise_for_status()
print(response.json())
package main
import (
"strings"
"fmt"
"io"
"net/http"
"os"
)
func main() {
body := strings.NewReader(`{
"quote_id": "qt_abc123",
"sig": "abc123...signature..."
}`)
request, err := http.NewRequest(http.MethodPost, "https://sandbox.payscribe.ng/api/v1/execute-quote", body)
if err != nil { panic(err) }
request.Header.Set("Authorization", "Bearer "+os.Getenv("PAYSCRIBE_API_KEY"))
request.Header.Set("Content-Type", "application/json")
response, err := http.DefaultClient.Do(request)
if err != nil { panic(err) }
defer response.Body.Close()
responseBody, _ := io.ReadAll(response.Body)
if response.StatusCode < 200 || response.StatusCode > 299 { panic(fmt.Sprintf("Payscribe request failed: %s", response.Status)) }
fmt.Println(string(responseBody))
}
Response
Status: 200 OK
{
"status": true,
"description": "Conversion executed successfully",
"message": {
"details": {
"conversion_id": "conv_def456",
"quote_id": "qt_abc123",
"from_currency": "USD",
"to_currency": "NGN",
"source_amount": 100,
"destination_amount": 153550,
"rate_used": 1535.5,
"fee_charged": 1500,
"status": "completed",
"reference": "PS_FX_ABC123",
"executed_at": "2026-07-20T11:56:00Z"
}
},
"status_code": 200
}
Verify Conversion
GET /verify-conversion/{id}
Verify the status and details of a currency conversion, by the trans_id a successful Execute Quote would return.
This endpoint's own logic works, but there is no way to reach it today: Execute Quote — the only thing that creates a conversion to look up — always fails in sandbox (see the caution there), so no trans_id is ever produced to verify.
Headers
| Field | Value |
|---|---|
| Authorization | Bearer PAYSCRIBE_API_KEY |
Path Parameters
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | The trans_id from a successful Execute Quote response |
Request
- cURL
- Node.js
- Python
- Go
curl -X GET "https://sandbox.payscribe.ng/api/v1/verify-conversion/conv_def456" \
-H "Authorization: Bearer $PAYSCRIBE_API_KEY"
const response = await fetch('https://sandbox.payscribe.ng/api/v1/verify-conversion/conv_def456', {
method: 'GET',
headers: {Authorization: `Bearer ${process.env.PAYSCRIBE_API_KEY}`},
});
const result = await response.json().catch(() => null);
if (!response.ok) throw new Error(`Payscribe request failed: ${response.status}`);
console.log(result);
import os
import requests
response = requests.get(
'https://sandbox.payscribe.ng/api/v1/verify-conversion/conv_def456',
headers={'Authorization': f"Bearer {os.environ['PAYSCRIBE_API_KEY']}"},
timeout=20,
)
response.raise_for_status()
print(response.json())
package main
import (
"fmt"
"io"
"net/http"
"os"
)
func main() {
var body io.Reader
request, err := http.NewRequest(http.MethodGet, "https://sandbox.payscribe.ng/api/v1/verify-conversion/conv_def456", body)
if err != nil { panic(err) }
request.Header.Set("Authorization", "Bearer "+os.Getenv("PAYSCRIBE_API_KEY"))
response, err := http.DefaultClient.Do(request)
if err != nil { panic(err) }
defer response.Body.Close()
responseBody, _ := io.ReadAll(response.Body)
if response.StatusCode < 200 || response.StatusCode > 299 { panic(fmt.Sprintf("Payscribe request failed: %s", response.Status)) }
fmt.Println(string(responseBody))
}
Response
Status: 200 OK
{
"status": true,
"description": "Currency conversion successful.",
"message": {
"details": {
"trans_id": "a3f2c9d1-7e4b-4f2a-9c3e-1b8d2f6a0c42",
"ref": "ref_abc123",
"amount_from": 100,
"amount_to": 153550,
"currency_from": "usd",
"currency_to": "ngn",
"rate": 1535.5,
"created_at": "2026-07-20T11:56:00Z",
"status": "success"
}
},
"status_code": 200
}
Was this page helpful?