Cards
Issue and manage virtual and physical USD cards, with optional stablecoin funding.
Create Card
Issue a new virtual or physical card for a customer.
Headers
| Header | Value |
|---|---|
Authorization | Bearer $PAYSCRIBE_API_KEY |
Content-Type | application/json |
Parameters
| Field | Type | Required | Description |
|---|---|---|---|
customer_id | string | Yes | Customer identifier |
currency | string | Yes | USD |
type | string | Yes | virtual, physical |
amount | number | Yes | Initial load amount |
payment_method | string | No | wallet (default) or stablecoin |
Request
POST /cards/create
- cURL
- Node.js
- Python
- Go
curl -X POST https://sandbox.payscribe.ng/api/v1/cards/create \
-H "Authorization: Bearer $PAYSCRIBE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"customer_id": "cus_xyz",
"currency": "USD",
"type": "virtual",
"amount": 50
}'
const response = await fetch('https://sandbox.payscribe.ng/api/v1/cards/create', {
method: 'POST',
headers: {Authorization: `Bearer ${process.env.PAYSCRIBE_API_KEY}`, 'Content-Type': 'application/json'},
body: JSON.stringify({
"customer_id": "cus_xyz",
"currency": "USD",
"type": "virtual",
"amount": 50
}),
});
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'''{
"customer_id": "cus_xyz",
"currency": "USD",
"type": "virtual",
"amount": 50
}''')
response = requests.post(
'https://sandbox.payscribe.ng/api/v1/cards/create',
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(`{
"customer_id": "cus_xyz",
"currency": "USD",
"type": "virtual",
"amount": 50
}`)
request, err := http.NewRequest(http.MethodPost, "https://sandbox.payscribe.ng/api/v1/cards/create", 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
201 Created
{
"status": true,
"description": "Card created successfully.",
"message": {
"details": {
"event_id": "1e6a0e2b-6f1d-4f2a-9b3e-2c9a6d0f1a2b",
"event_type": "issuing.created.successful",
"trans_id": "3b2f9c7a-4d5e-4a1b-8c6d-9e0f1a2b3c4d",
"ref": "ref_abc123",
"card": {
"id": "card_abc123",
"card_type": "virtual",
"currency": "USD",
"brand": "VISA",
"name": "JOHN DOE",
"first_six": "428852",
"last_four": "1234",
"masked": "428852 **** **** 1234",
"secure_details": {
"alg": "AES-256-GCM",
"iv": "base64-iv",
"tag": "base64-tag",
"data": "base64-ciphertext",
"aad": "bid:123|env:sandbox|card:card_abc123"
},
"billing": {
"street": "220 KARAND",
"city": "Yugau",
"state": "JAWA",
"country": "ID",
"postal_code": "8299"
},
"created_at": "2026-07-20T10:30:00.000Z",
"updated_at": "2026-07-20T10:30:00.000Z"
},
"customer": {
"id": "cus_xyz",
"name": "JOHN DOE"
}
}
},
"status_code": 201
}
The card number, CVV, and expiry are never returned in plaintext. card.secure_details is an AES-256-GCM ciphertext (iv, tag, data, and an aad string of bid/env/card values you must reproduce) encrypted with your business's Merchant Hash Key, generated under Settings → API Keys; decrypt it server-side to retrieve those fields.
The decryption contract for secure_details is not yet written up as a guide. Contact support for the current decryption steps before relying on this field in production.
Top Up Card
Add funds to an existing card.
Headers
| Header | Value |
|---|---|
Authorization | Bearer $PAYSCRIBE_API_KEY |
Content-Type | application/json |
Parameters
| Field | Type | Required | Description |
|---|---|---|---|
amount | number | Yes | Amount to add to the card |
ref | string | Yes | Unique merchant transaction reference |
Request
PATCH /cards/{id}/topup
- cURL
- Node.js
- Python
- Go
curl -X PATCH https://sandbox.payscribe.ng/api/v1/cards/card_abc123/topup \
-H "Authorization: Bearer $PAYSCRIBE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"amount": 100, "ref": "CARD-TOPUP-001"}'
const response = await fetch('https://sandbox.payscribe.ng/api/v1/cards/card_abc123/topup', {
method: 'PATCH',
headers: {Authorization: `Bearer ${process.env.PAYSCRIBE_API_KEY}`, 'Content-Type': 'application/json'},
body: JSON.stringify({
"amount": 100,
"ref": "CARD-TOPUP-001"
}),
});
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'''{
"amount": 100,
"ref": "CARD-TOPUP-001"
}''')
response = requests.patch(
'https://sandbox.payscribe.ng/api/v1/cards/card_abc123/topup',
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(`{
"amount": 100,
"ref": "CARD-TOPUP-001"
}`)
request, err := http.NewRequest(http.MethodPatch, "https://sandbox.payscribe.ng/api/v1/cards/card_abc123/topup", 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
200 OK
{
"status": true,
"description": "Card topup successfully.",
"message": {
"details": {
"trans_id": "3b2f9c7a-4d5e-4a1b-8c6d-9e0f1a2b3c4d",
"ref": "CARD-TOPUP-001",
"customer": {"id": "cus_xyz"},
"card": {
"id": "card_abc123",
"first_six": "428852",
"last_four": "1234",
"brand": "VISA",
"prev_balance": 50,
"balance": 150
},
"currency": "USD",
"prev_balance": 0,
"balance": 0,
"ref_id": "CARD-TOPUP-001",
"action": "topup",
"status": "success",
"created_at": "2026-07-20 10:30:00"
}
},
"status_code": 200
}
The card's new balance is card.balance, not a top-level balance — the top-level prev_balance/balance are always 0 on this endpoint (a quirk of the current implementation, not something to rely on).
Withdraw from Card
Withdraw funds from a card back to the issuing wallet.
Headers
| Header | Value |
|---|---|
Authorization | Bearer $PAYSCRIBE_API_KEY |
Content-Type | application/json |
Parameters
| Field | Type | Required | Description |
|---|---|---|---|
amount | number | Yes | Amount to withdraw from the card |
ref | string | Yes | Unique merchant transaction reference |
Request
PATCH /cards/{id}/withdraw
- cURL
- Node.js
- Python
- Go
curl -X PATCH https://sandbox.payscribe.ng/api/v1/cards/card_abc123/withdraw \
-H "Authorization: Bearer $PAYSCRIBE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"amount": 25, "ref": "CARD-WITHDRAW-001"}'
const response = await fetch('https://sandbox.payscribe.ng/api/v1/cards/card_abc123/withdraw', {
method: 'PATCH',
headers: {Authorization: `Bearer ${process.env.PAYSCRIBE_API_KEY}`, 'Content-Type': 'application/json'},
body: JSON.stringify({
"amount": 25,
"ref": "CARD-WITHDRAW-001"
}),
});
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'''{
"amount": 25,
"ref": "CARD-WITHDRAW-001"
}''')
response = requests.patch(
'https://sandbox.payscribe.ng/api/v1/cards/card_abc123/withdraw',
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(`{
"amount": 25,
"ref": "CARD-WITHDRAW-001"
}`)
request, err := http.NewRequest(http.MethodPatch, "https://sandbox.payscribe.ng/api/v1/cards/card_abc123/withdraw", 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
200 OK
{
"status": true,
"description": "Card withdraw successfully.",
"message": {
"details": {
"trans_id": "8f1a2b3c-4d5e-4a1b-8c6d-9e0f1a2b3c4d",
"ref": "CARD-WITHDRAW-001",
"customer": {"id": "cus_xyz"},
"card": {
"id": "card_abc123",
"first_six": "428852",
"last_four": "1234",
"brand": "VISA",
"prev_balance": 150,
"balance": 125
},
"currency": "USD",
"prev_balance": 0,
"balance": 0,
"ref_id": "CARD-WITHDRAW-001",
"action": "withdraw",
"status": "success",
"created_at": "2026-07-20 10:30:00"
}
},
"status_code": 200
}
As with top-up, the card's new balance is card.balance, not a top-level balance.
Get Card Details
Retrieve details of a specific card.
Headers
| Header | Value |
|---|---|
Authorization | Bearer $PAYSCRIBE_API_KEY |
Request
GET /cards/{id}
- cURL
- Node.js
- Python
- Go
curl -X GET https://sandbox.payscribe.ng/api/v1/cards/card_abc123 \
-H "Authorization: Bearer $PAYSCRIBE_API_KEY"
const response = await fetch('https://sandbox.payscribe.ng/api/v1/cards/card_abc123', {
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/cards/card_abc123',
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/cards/card_abc123", 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
200 OK
{
"status": true,
"description": "Card details fetched successfully.",
"message": {
"details": {
"id": "card_abc123",
"currency": "USD",
"card_type": "virtual",
"brand": "VISA",
"first_six": "428852",
"last_four": "1234",
"masked": "428852 **** **** 1234",
"card_number": "4288521234561234",
"expiry": "12/28",
"ccv": "123",
"balance": 125,
"status": "active",
"billing": {
"address": "220 KARAND",
"country": "NG",
"state": "Lagos",
"city": "Lagos",
"postal_code": "100001"
},
"created_at": "2026-07-20 10:30:00",
"updated_at": "2026-07-20 10:30:00",
"terminate": false,
"terminate_date": null,
"customer": {
"id": "cus_xyz",
"name": "John Doe"
}
}
},
"status_code": 200
}
On Sandbox, this endpoint returns card_number, expiry, and ccv in plaintext. On Live, the same endpoint instead returns an encrypted secure_details object (see Create Card) — there is no plaintext PAN/CVV field in production. Don't design your integration around the sandbox shape; branch on environment if you inspect these fields directly.
There is no top-level customer_id, type, expiry_month, or expiry_year on this endpoint — use customer.id, card_type, and expiry instead.
Get Card Transactions
Retrieve transaction history for a card.
Headers
| Header | Value |
|---|---|
Authorization | Bearer $PAYSCRIBE_API_KEY |
Parameters
| Field | Type | Required | Description |
|---|---|---|---|
start_date | string | No | Filter by start date (YYYY-MM-DD) |
end_date | string | No | Filter by end date (YYYY-MM-DD) |
page | integer | No | Page number for pagination |
page_size | integer | No | Number of records per page |
Request
GET /cards/{id}/transactions
- cURL
- Node.js
- Python
- Go
curl -X GET "https://sandbox.payscribe.ng/api/v1/cards/card_abc123/transactions?page=1&page_size=20" \
-H "Authorization: Bearer $PAYSCRIBE_API_KEY"
const response = await fetch('https://sandbox.payscribe.ng/api/v1/cards/card_abc123/transactions?page=1&page_size=20', {
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/cards/card_abc123/transactions?page=1&page_size=20',
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/cards/card_abc123/transactions?page=1&page_size=20", 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
200 OK
{
"status": true,
"description": "Card Transactions Fetched successfully.",
"message": {
"details": {
"transactions": [
{
"currency": "USD",
"name": "John Doe",
"masked": "428852 **** **** 1234",
"balance": 125,
"status": "success",
"ref_id": "CARD-TOPUP-001",
"ref_type": "cards",
"amount": 100,
"created_at": "2026-07-19 14:22:00",
"trans_id": "3b2f9c7a-4d5e-4a1b-8c6d-9e0f1a2b3c4d",
"description": "Sandbox card topup for VISA 428852 **** 1234"
}
],
"total": 1,
"page": 1,
"page_size": 20
}
},
"status_code": 200
}
The row shape is {currency, name, masked, balance, status, ref_id, ref_type, amount, created_at, trans_id, description} — there is no id, card_id, type, or merchant field, and pagination (total/page/page_size) sits alongside transactions, not nested under a pagination key. Note start_date/end_date are currently accepted but not applied as filters by this endpoint — every transaction for the card is returned regardless of the date range passed.
Get All Cards
List every virtual card issued to your business, newest first.
This is a live-only endpoint, so call it against api.payscribe.ng — there is no sandbox counterpart.
Headers
| Header | Value |
|---|---|
Authorization | Bearer $PAYSCRIBE_API_KEY |
Parameters
| Field | Type | Required | Description |
|---|---|---|---|
page | integer | No | Page number, starting at 1. Default 1. |
page_size | integer | No | Results per page, 1–100. Default 25. |
status | string | No | Filter by card status (e.g. active, frozen, terminated). |
customer_id | string | No | Only cards assigned to this customer slug. |
brand | string | No | Only cards of this brand (e.g. VISA). |
Request
GET /cards
- cURL
- Node.js
- Python
- Go
curl -G https://api.payscribe.ng/api/v1/cards \
-H "Authorization: Bearer $PAYSCRIBE_API_KEY" \
--data-urlencode "page=1" \
--data-urlencode "page_size=25"
const response = await fetch('https://api.payscribe.ng/api/v1/cards?page=1&page_size=25', {
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}`);
import os
import requests
response = requests.get(
'https://api.payscribe.ng/api/v1/cards?page=1&page_size=25',
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://api.payscribe.ng/api/v1/cards?page=1&page_size=25", 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
200 OK
{
"status": true,
"description": "Cards fetched successfully.",
"message": {
"details": [
[
{
"id": "card_abc123",
"brand": "VISA",
"masked": "512345 **** **** 6789",
"last_four": "6789",
"currency": "USD",
"balance": 125,
"status": "active",
"contactless": false,
"customer": {
"id": "cus_xyz",
"name": "John Doe"
},
"created_at": "2026-07-20 10:30:00"
}
],
{
"page": 1,
"page_size": 25,
"count": 1
}
]
},
"status_code": 200
}
The payload is an array [cards, pagination] — details[0] is the list of cards and details[1] holds page/page_size/count (total matching cards). Each card row is {id, brand, masked, last_four, currency, balance, status, contactless, customer, created_at}; unlike Get Card Details there is no first_six or secure_details in this listing.
Freeze Card
Temporarily freeze a card to block transactions.
Send a unique ref for the status change.
Headers
| Header | Value |
|---|---|
Authorization | Bearer $PAYSCRIBE_API_KEY |
Request
PATCH /cards/{id}/freeze
- cURL
- Node.js
- Python
- Go
curl -X PATCH https://sandbox.payscribe.ng/api/v1/cards/card_abc123/freeze \
-H "Authorization: Bearer $PAYSCRIBE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"ref":"CARD-FREEZE-001"}'
const response = await fetch('https://sandbox.payscribe.ng/api/v1/cards/card_abc123/freeze', {
method: 'PATCH',
headers: {Authorization: `Bearer ${process.env.PAYSCRIBE_API_KEY}`, 'Content-Type': 'application/json'},
body: JSON.stringify({ref: 'CARD-FREEZE-001'}),
});
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.patch(
'https://sandbox.payscribe.ng/api/v1/cards/card_abc123/freeze',
headers={'Authorization': f"Bearer {os.environ['PAYSCRIBE_API_KEY']}"},
json={'ref': 'CARD-FREEZE-001'},
timeout=20,
)
response.raise_for_status()
print(response.json())
package main
import (
"fmt"
"io"
"net/http"
"os"
"strings"
)
func main() {
body := strings.NewReader(`{"ref":"CARD-FREEZE-001"}`)
request, err := http.NewRequest(http.MethodPatch, "https://sandbox.payscribe.ng/api/v1/cards/card_abc123/freeze", 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
200 OK
{
"status": true,
"description": "Card action - freeze successful.",
"message": {
"details": {
"trans_id": "9c8b7a6d-5e4f-4a1b-8c6d-9e0f1a2b3c4d",
"ref": "CARD-FREEZE-001",
"customer": {"id": "cus_xyz"},
"card": {
"id": "card_abc123",
"first_six": "428852",
"last_four": "1234",
"prev_balance": 125,
"balance": 125
},
"currency": "USD",
"action": "freeze",
"created_at": "2026-07-20 10:30:00"
}
},
"status_code": 200
}
There is no top-level id or status field — the card's identifier is card.id and there is no status field at all in this response; check the card's status via Get Card Details if you need to confirm it.
Unfreeze Card
Unfreeze a previously frozen card.
Send a unique ref for the status change.
Headers
| Header | Value |
|---|---|
Authorization | Bearer $PAYSCRIBE_API_KEY |
Request
PATCH /cards/{id}/unfreeze
- cURL
- Node.js
- Python
- Go
curl -X PATCH https://sandbox.payscribe.ng/api/v1/cards/card_abc123/unfreeze \
-H "Authorization: Bearer $PAYSCRIBE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"ref":"CARD-UNFREEZE-001"}'
const response = await fetch('https://sandbox.payscribe.ng/api/v1/cards/card_abc123/unfreeze', {
method: 'PATCH',
headers: {Authorization: `Bearer ${process.env.PAYSCRIBE_API_KEY}`, 'Content-Type': 'application/json'},
body: JSON.stringify({ref: 'CARD-UNFREEZE-001'}),
});
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.patch(
'https://sandbox.payscribe.ng/api/v1/cards/card_abc123/unfreeze',
headers={'Authorization': f"Bearer {os.environ['PAYSCRIBE_API_KEY']}"},
json={'ref': 'CARD-UNFREEZE-001'},
timeout=20,
)
response.raise_for_status()
print(response.json())
package main
import (
"fmt"
"io"
"net/http"
"os"
"strings"
)
func main() {
body := strings.NewReader(`{"ref":"CARD-UNFREEZE-001"}`)
request, err := http.NewRequest(http.MethodPatch, "https://sandbox.payscribe.ng/api/v1/cards/card_abc123/unfreeze", 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
200 OK
{
"status": true,
"description": "Card action - unfreeze successful.",
"message": {
"details": {
"trans_id": "1a2b3c4d-5e6f-4a1b-8c6d-9e0f1a2b3c4d",
"ref": "CARD-UNFREEZE-001",
"customer": {"id": "cus_xyz"},
"card": {
"id": "card_abc123",
"first_six": "428852",
"last_four": "1234",
"prev_balance": 125,
"balance": 125
},
"currency": "USD",
"action": "unfreeze",
"created_at": "2026-07-20 10:30:00"
}
},
"status_code": 200
}
As with Freeze, there is no top-level id/status field. Outside Sandbox, unfreezing is also blocked with a 422 if the business has a USD due balance greater than 5.
Replace Card
Replace a lost, stolen, or damaged card.
Headers
| Header | Value |
|---|---|
Authorization | Bearer $PAYSCRIBE_API_KEY |
Request
PATCH /cards/replace/{id}
- cURL
- Node.js
- Python
- Go
curl -X PATCH https://api.payscribe.ng/api/v1/cards/replace/card_abc123 \
-H "Authorization: Bearer $PAYSCRIBE_API_KEY"
const response = await fetch('https://api.payscribe.ng/api/v1/cards/replace/card_abc123', {
method: 'PATCH',
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.patch(
'https://api.payscribe.ng/api/v1/cards/replace/card_abc123',
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.MethodPatch, "https://api.payscribe.ng/api/v1/cards/replace/card_abc123", 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
200 OK
{
"status": true,
"description": "Card replaced successfully.",
"message": {
"details": {
"event_id": "5e6f7a8b-4d5e-4a1b-8c6d-9e0f1a2b3c4d",
"event_type": "issuing.replaced.successful",
"trans_id": "5e6f7a8b-4d5e-4a1b-8c6d-9e0f1a2b3c4d",
"ref": null,
"card": {
"id": "card_abc123",
"card_type": "virtual",
"currency": "USD",
"brand": "VISA",
"name": "JOHN DOE",
"first_six": "512345",
"last_four": "6789",
"masked": "512345 **** **** 6789",
"secure_details": {
"alg": "AES-256-GCM",
"iv": "base64-iv",
"tag": "base64-tag",
"data": "base64-ciphertext",
"aad": "bid:123|env:live|card:card_abc123"
},
"billing": {
"street": "220 KARAND",
"city": "Yugau",
"state": "JAWA",
"country": "ID",
"postal_code": "8299"
},
"created_at": "2026-07-20 10:30:00",
"updated_at": "2026-07-20 10:30:00"
},
"customer": {
"id": "cus_xyz",
"name": "John Doe"
}
}
},
"status_code": 200
}
The card keeps the same card.id (card_uuid) but gets a new PAN/CVV/expiry — as with Create Card, those are only available encrypted inside card.secure_details, decrypted with your Merchant Hash Key. There is no old_card_id/new_card_id/status field on this endpoint.
Terminate Card
Permanently close a card.
Headers
| Header | Value |
|---|---|
Authorization | Bearer $PAYSCRIBE_API_KEY |
Content-Type | application/json |
Parameters
| Field | Type | Required | Description |
|---|---|---|---|
ref | string | Yes | Unique merchant transaction reference for the status change. |
Request
POST /cards/{id}/terminate
- cURL
- Node.js
- Python
- Go
curl -X POST https://sandbox.payscribe.ng/api/v1/cards/card_abc123/terminate \
-H "Authorization: Bearer $PAYSCRIBE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"ref":"CARD-TERMINATE-001"}'
const response = await fetch('https://sandbox.payscribe.ng/api/v1/cards/card_abc123/terminate', {
method: 'POST',
headers: {Authorization: `Bearer ${process.env.PAYSCRIBE_API_KEY}`, 'Content-Type': 'application/json'},
body: JSON.stringify({ref: 'CARD-TERMINATE-001'}),
});
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.post(
'https://sandbox.payscribe.ng/api/v1/cards/card_abc123/terminate',
headers={'Authorization': f"Bearer {os.environ['PAYSCRIBE_API_KEY']}"},
json={'ref': 'CARD-TERMINATE-001'},
timeout=20,
)
response.raise_for_status()
print(response.json())
package main
import (
"fmt"
"io"
"net/http"
"os"
"strings"
)
func main() {
body := strings.NewReader(`{"ref":"CARD-TERMINATE-001"}`)
request, err := http.NewRequest(http.MethodPost, "https://sandbox.payscribe.ng/api/v1/cards/card_abc123/terminate", 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
200 OK
{
"status": true,
"description": "Card action - terminate successful.",
"message": {
"details": {
"trans_id": "2b3c4d5e-4d5e-4a1b-8c6d-9e0f1a2b3c4d",
"ref": "CARD-TERMINATE-001",
"customer": {"id": "cus_xyz"},
"card": {
"id": "card_abc123",
"first_six": "428852",
"last_four": "1234",
"prev_balance": 125,
"balance": 0
},
"currency": "USD",
"action": "terminate",
"created_at": "2026-07-20 10:30:00"
}
},
"status_code": 200
}
Like Freeze/Unfreeze, this endpoint requires a ref in the request body — it is not optional, despite POST /cards/{id}/terminate taking no other input. There is no top-level id/status field; the card's balance is zeroed out on termination (card.balance).
Update Card Contact
Update the contact information (phone, email) associated with a card.
Headers
| Header | Value |
|---|---|
Authorization | Bearer $PAYSCRIBE_API_KEY |
Content-Type | application/json |
Parameters
| Field | Type | Required | Description |
|---|---|---|---|
mobile | string | No | Cardholder phone number. Falls back to the customer's phone on file. |
email | string | No | Cardholder email address. Falls back to the customer's email on file. |
billing_details | object | No | address1, address2, city, state, zipcode, country. Falls back to the customer's address on file. |
At least one of mobile/email must resolve to a non-empty value (from the request or the customer record), or the request fails with 422.
Request
PATCH /cards/{id}/card-contact
- cURL
- Node.js
- Python
- Go
curl -X PATCH https://api.payscribe.ng/api/v1/cards/card_abc123/card-contact \
-H "Authorization: Bearer $PAYSCRIBE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"mobile": "+2348012345678", "email": "cardholder@example.com"}'
const response = await fetch('https://api.payscribe.ng/api/v1/cards/card_abc123/card-contact', {
method: 'PATCH',
headers: {Authorization: `Bearer ${process.env.PAYSCRIBE_API_KEY}`, 'Content-Type': 'application/json'},
body: JSON.stringify({
"mobile": "+2348012345678",
"email": "cardholder@example.com"
}),
});
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'''{
"mobile": "+2348012345678",
"email": "cardholder@example.com"
}''')
response = requests.patch(
'https://api.payscribe.ng/api/v1/cards/card_abc123/card-contact',
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(`{
"mobile": "+2348012345678",
"email": "cardholder@example.com"
}`)
request, err := http.NewRequest(http.MethodPatch, "https://api.payscribe.ng/api/v1/cards/card_abc123/card-contact", 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
200 OK
{
"status": true,
"description": "Card contact details updated successfully.",
"message": {
"details": {
"card_id": "card_abc123",
"email": "payscribe-card-4821.payscribe.co",
"mobile": "+2348012345678",
"billing_details": {
"address1": "220 KARAND",
"address2": "",
"city": "Yugau",
"state": "JAWA",
"zipcode": "8299",
"country": "ID"
},
"trans_id": "6f7a8b9c-4d5e-4a1b-8c6d-9e0f1a2b3c4d"
}
},
"status_code": 200
}
The email field currently always echoes a generated placeholder address (payscribe-card-<id>.payscribe.co), not the address you submitted — the submitted email is sent to the card processor but is not returned back to you. Don't rely on the echoed email value.
There is no id/contact field; the field name is mobile, not phone, and there is no top-level id object — use card_id.
Stablecoin-Funded Cards
Fund a new card with USDT/USDC instead of your USD wallet balance. Add "payment_method": "stablecoin" to the create request — this does not create the card immediately; it returns a deposit address, and the card is created automatically once your CoinsBuy deposit is confirmed. (Card top-ups also support "payment_method": "stablecoin" on PATCH /cards/{id}/topup — the same pending-deposit pattern applies there; withdrawals don't support it and must use the USD wallet.)
Headers
| Header | Value |
|---|---|
Authorization | Bearer $PAYSCRIBE_API_KEY |
Content-Type | application/json |
Parameters
In addition to the Create Card parameters:
| Field | Type | Required | Description |
|---|---|---|---|
payment_method | string | Yes | Must be stablecoin to use this flow. |
stablecoin_currency | string | No | USDT or USDC. Defaults to USDT. |
stablecoin_network | string | No | Defaults to Tron. |
stablecoin_chain | string | No | Defaults to TRC20. |
Request
POST /cards/create
- cURL
- Node.js
- Python
- Go
curl -X POST https://sandbox.payscribe.ng/api/v1/cards/create \
-H "Authorization: Bearer $PAYSCRIBE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"customer_id": "cus_xyz",
"amount": 100,
"ref": "CARD-STABLE-001",
"payment_method": "stablecoin",
"stablecoin_currency": "USDT",
"stablecoin_network": "Tron",
"stablecoin_chain": "TRC20"
}'
const response = await fetch('https://sandbox.payscribe.ng/api/v1/cards/create', {
method: 'POST',
headers: {Authorization: `Bearer ${process.env.PAYSCRIBE_API_KEY}`, 'Content-Type': 'application/json'},
body: JSON.stringify({
"customer_id": "cus_xyz",
"amount": 100,
"ref": "CARD-STABLE-001",
"payment_method": "stablecoin",
"stablecoin_currency": "USDT",
"stablecoin_network": "Tron",
"stablecoin_chain": "TRC20"
}),
});
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'''{
"customer_id": "cus_xyz",
"amount": 100,
"ref": "CARD-STABLE-001",
"payment_method": "stablecoin",
"stablecoin_currency": "USDT",
"stablecoin_network": "Tron",
"stablecoin_chain": "TRC20"
}''')
response = requests.post(
'https://sandbox.payscribe.ng/api/v1/cards/create',
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(`{
"customer_id": "cus_xyz",
"amount": 100,
"ref": "CARD-STABLE-001",
"payment_method": "stablecoin",
"stablecoin_currency": "USDT",
"stablecoin_network": "Tron",
"stablecoin_chain": "TRC20"
}`)
request, err := http.NewRequest(http.MethodPost, "https://sandbox.payscribe.ng/api/v1/cards/create", 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
200 OK
{
"status": true,
"description": "Deposit address ready. Your card will be created once the deposit is confirmed.",
"message": {
"details": {
"order_id": "7c8d9e0f-4d5e-4a1b-8c6d-9e0f1a2b3c4d",
"status": "pending_payment",
"payment_method": "stablecoin",
"address": "TQ8wnAJoPyjYnB7xQgsbqyqJQxrkZ9mw3J",
"memo_tag": null,
"currency": "USDT",
"network": "Tron",
"chain": "TRC20",
"amount": 102,
"fee": 2,
"payment_link": "https://links.payscribe.co/coin/7c8d9e0f-4d5e-4a1b-8c6d-9e0f1a2b3c4d",
"expires_at": "2026-07-21T10:30:00+00:00"
}
},
"status_code": 200
}
This response does not contain a card object — no card exists yet. Send the stablecoin deposit to address (with memo_tag if present) before expires_at; the card is created once the deposit is confirmed, and delivered via the issuing.created.successful webhook at that point, in the same shape as Create Card's response. amount includes the card-creation fee.
Webhooks
| Event | Description |
|---|---|
cards.auth.activation | Contactless card activated at POS |
cards.auth.approved | Card authorization approved |
cards.auth.settled | Card authorization settled (captured) |
cards.auth.declined | Card authorization declined |
cards.auth.verified | Card verification ($1 auth) approved |
cards.adjusted.refund | Card transaction refunded |
cards.adjusted.crossborder | Cross-border transaction settled |
cards.adjusted.topup | Card topped up |
cards.adjusted.withdraw | Card withdrawal |
cards.adjusted.freeze | Card frozen |
cards.adjusted.unfreeze | Card unfrozen |
cards.adjusted.terminated | Card terminated |
issuing.created.successful | Card created (including stablecoin-funded orders, once the deposit is confirmed) |
issuing.replaced.successful | Card replaced |
See Webhooks for payload format and verification.
Was this page helpful?