curl --request POST \
--url https://api.globalstack.io/v1/wallets \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"currency": "<string>",
"country_code": "<string>",
"customer_id": "<string>",
"destination_address": "<string>",
"label": "<string>",
"metadata": {}
}
'import requests
url = "https://api.globalstack.io/v1/wallets"
payload = {
"currency": "<string>",
"country_code": "<string>",
"customer_id": "<string>",
"destination_address": "<string>",
"label": "<string>",
"metadata": {}
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
currency: '<string>',
country_code: '<string>',
customer_id: '<string>',
destination_address: '<string>',
label: '<string>',
metadata: {}
})
};
fetch('https://api.globalstack.io/v1/wallets', 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.globalstack.io/v1/wallets",
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([
'currency' => '<string>',
'country_code' => '<string>',
'customer_id' => '<string>',
'destination_address' => '<string>',
'label' => '<string>',
'metadata' => [
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$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.globalstack.io/v1/wallets"
payload := strings.NewReader("{\n \"currency\": \"<string>\",\n \"country_code\": \"<string>\",\n \"customer_id\": \"<string>\",\n \"destination_address\": \"<string>\",\n \"label\": \"<string>\",\n \"metadata\": {}\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
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.globalstack.io/v1/wallets")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"currency\": \"<string>\",\n \"country_code\": \"<string>\",\n \"customer_id\": \"<string>\",\n \"destination_address\": \"<string>\",\n \"label\": \"<string>\",\n \"metadata\": {}\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.globalstack.io/v1/wallets")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"currency\": \"<string>\",\n \"country_code\": \"<string>\",\n \"customer_id\": \"<string>\",\n \"destination_address\": \"<string>\",\n \"label\": \"<string>\",\n \"metadata\": {}\n}"
response = http.request(request)
puts response.read_body{
"status": true,
"message": "Wallet created successfully",
"code": "ok",
"data": {
"id": "wal_01HXYZABC1234567890ABCDEFG",
"customer_id": "cust_01HXYZABC1234567890ABCDEFG",
"customer": {
"id": "cust_01HXYZABC1234567890ABCDEFG",
"type": "individual",
"name": "Alice Johnson",
"email": "alice@example.com",
"tier": "tier_2",
"created_at": "2026-04-16T16:14:08.000Z",
"updated_at": "2026-04-16T16:14:08.000Z"
},
"currency": "USDC",
"type": "custodial",
"available_balance": "1234.567890",
"pending_balance": "0.000000",
"total_balance": "1234.567890",
"metadata": {},
"provisioned": false,
"created_at": "2026-05-20T12:00:00.000Z",
"updated_at": "2026-05-20T12:00:00.000Z",
"country_code": "NG",
"label": "USDC Operating Wallet",
"destination_address": null,
"destination_chain": null
},
"meta": {
"request_id": "req_01HXYZ4K5ABCDEFGHJKLMNPQRS",
"timestamp": "2026-05-14T15:43:55.732Z",
"version": "1"
}
}{
"status": false,
"message": "Resource not found",
"code": "not_found",
"data": null,
"meta": {
"next_steps": {
"action": "Verify the resource identifier and retry.",
"docs_url": "https://docs.globalstack.io/errors#not_found"
},
"request_id": "req_01HXYZ4K5ABCDEFGHJKLMNPQRS",
"timestamp": "2026-05-14T15:43:55.732Z",
"version": "1"
}
}{
"status": false,
"message": "Resource not found",
"code": "not_found",
"data": null,
"meta": {
"next_steps": {
"action": "Verify the resource identifier and retry.",
"docs_url": "https://docs.globalstack.io/errors#not_found"
},
"request_id": "req_01HXYZ4K5ABCDEFGHJKLMNPQRS",
"timestamp": "2026-05-14T15:43:55.732Z",
"version": "1"
}
}{
"status": false,
"message": "Resource not found",
"code": "not_found",
"data": null,
"meta": {
"next_steps": {
"action": "Verify the resource identifier and retry.",
"docs_url": "https://docs.globalstack.io/errors#not_found"
},
"request_id": "req_01HXYZ4K5ABCDEFGHJKLMNPQRS",
"timestamp": "2026-05-14T15:43:55.732Z",
"version": "1"
}
}{
"status": false,
"message": "Resource not found",
"code": "not_found",
"data": null,
"meta": {
"next_steps": {
"action": "Verify the resource identifier and retry.",
"docs_url": "https://docs.globalstack.io/errors#not_found"
},
"request_id": "req_01HXYZ4K5ABCDEFGHJKLMNPQRS",
"timestamp": "2026-05-14T15:43:55.732Z",
"version": "1"
}
}Create a wallet
Create a wallet — a ledger balance in a single currency for a customer.
This does not create a credential. Add funding rails later with POST /v1/wallets/:id/credentials when you need a persistent receive identity. You can still onramp into a wallet with zero credentials.
If customer_id is omitted, the wallet is created for the merchant.
The currency must support deposit (can_deposit = true).
country_code (optional, ISO 3166-1 alpha-2, e.g. NG) pins the wallet to a country
at creation. It is required only when the same currency code is supported in more than
one country; a single-country currency code (like KES in KE) resolves without it. CRYPTO
currencies (like USDC) always resolve to null — supplying country_code for a crypto
code has no effect. The pinned country is persisted on the wallet, echoed on every wallet
response as country_code, and used by downstream flows (quotes, deposit-addresses) instead
of the customer’s registered country — so a wallet can serve payers who transact from a
country other than the customer’s KYC country.
Balances start at zero, formatted to the currency’s precision. A customer may
hold multiple wallets in the same crypto currency, but only one wallet per fiat
currency; each create returns a new wallet with its own wal_… id. Use the
optional label to tell them apart.
Wallet type
type chooses the wallet’s shape and is fixed at creation.
custodial(default) — the wallet holds a balance. Deposits credit it, and you convert, offramp and transfer out of it. This is how every wallet behaved beforetypeexisted, so omittingtypekeeps current behaviour.forwarding— the wallet holds no balance. Every inflow is delivered straight todestination_addressondestination_chain, an address you control.
A forwarding wallet requires a crypto currency and both destination fields. Its balances always
read zero, and it cannot be the source of a transfer, offramp or conversion — those return
400 invalid_input. Neither destination field can be changed afterwards: create a new wallet to
change where funds land.
Body
currency— required. 2-8 uppercase alphanumerics (e.g.USDC,NGN).country_code— optional. 2-letter uppercase ISO 3166-1 alpha-2 (e.g.NG).customer_id— Owner customer ID. If omitted, defaults to your account’s default customer — created automatically when you signed up.type— optional.custodial(default) orforwarding.destination_address— required whentypeisforwarding, rejected otherwise. Max 128 chars, validated againstdestination_chain(EVM chains expect0x+ 40 hex; Solana expects a base58 address).destination_chain— required whentypeisforwarding, rejected otherwise. One ofsolana,base,ethereum, and must be supported bycurrency.label— optional. Max 255 chars.metadata— optional. Free-formRecord<string, string>.
Owner
data.customer is the customer that owns the wallet, returned on every wallet payload:
| Field | Description |
|---|---|
id | Customer external id (cust_…), identical to customer_id. |
type | individual or business. |
name | Customer full or business name. |
email | Customer email address. |
tier | KYC tier — tier_0, tier_1 or tier_2. |
created_at / updated_at | ISO-8601 timestamps. |
customer_id is unchanged and still present. For KYC status, metadata or the customer profile,
call GET /v1/customers/{customerId}.
Errors
400 invalid_input(was404 not_foundbefore XB-1154) —currency 'X' is not supported. The code isn’t in the supported currency table at all.400 invalid_input—currency 'X' is not supported in country 'Y'. The code exists but the suppliedcountry_codedoesn’t match any row.400 invalid_input—currency 'X' is available in multiple countries (A, B); country_code is required. Supply the pin to disambiguate.
Example
POST /v1/wallets
{
"currency": "USDC"
}
POST /v1/wallets
{
"currency": "NGN",
"country_code": "NG"
}
A forwarding wallet — every deposit is delivered to your own address on Base:
POST /v1/wallets
{
"currency": "USDC",
"type": "forwarding",
"destination_address": "0x1111111111111111111111111111111111111111",
"destination_chain": "base"
}
curl --request POST \
--url https://api.globalstack.io/v1/wallets \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"currency": "<string>",
"country_code": "<string>",
"customer_id": "<string>",
"destination_address": "<string>",
"label": "<string>",
"metadata": {}
}
'import requests
url = "https://api.globalstack.io/v1/wallets"
payload = {
"currency": "<string>",
"country_code": "<string>",
"customer_id": "<string>",
"destination_address": "<string>",
"label": "<string>",
"metadata": {}
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
currency: '<string>',
country_code: '<string>',
customer_id: '<string>',
destination_address: '<string>',
label: '<string>',
metadata: {}
})
};
fetch('https://api.globalstack.io/v1/wallets', 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.globalstack.io/v1/wallets",
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([
'currency' => '<string>',
'country_code' => '<string>',
'customer_id' => '<string>',
'destination_address' => '<string>',
'label' => '<string>',
'metadata' => [
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$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.globalstack.io/v1/wallets"
payload := strings.NewReader("{\n \"currency\": \"<string>\",\n \"country_code\": \"<string>\",\n \"customer_id\": \"<string>\",\n \"destination_address\": \"<string>\",\n \"label\": \"<string>\",\n \"metadata\": {}\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
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.globalstack.io/v1/wallets")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"currency\": \"<string>\",\n \"country_code\": \"<string>\",\n \"customer_id\": \"<string>\",\n \"destination_address\": \"<string>\",\n \"label\": \"<string>\",\n \"metadata\": {}\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.globalstack.io/v1/wallets")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"currency\": \"<string>\",\n \"country_code\": \"<string>\",\n \"customer_id\": \"<string>\",\n \"destination_address\": \"<string>\",\n \"label\": \"<string>\",\n \"metadata\": {}\n}"
response = http.request(request)
puts response.read_body{
"status": true,
"message": "Wallet created successfully",
"code": "ok",
"data": {
"id": "wal_01HXYZABC1234567890ABCDEFG",
"customer_id": "cust_01HXYZABC1234567890ABCDEFG",
"customer": {
"id": "cust_01HXYZABC1234567890ABCDEFG",
"type": "individual",
"name": "Alice Johnson",
"email": "alice@example.com",
"tier": "tier_2",
"created_at": "2026-04-16T16:14:08.000Z",
"updated_at": "2026-04-16T16:14:08.000Z"
},
"currency": "USDC",
"type": "custodial",
"available_balance": "1234.567890",
"pending_balance": "0.000000",
"total_balance": "1234.567890",
"metadata": {},
"provisioned": false,
"created_at": "2026-05-20T12:00:00.000Z",
"updated_at": "2026-05-20T12:00:00.000Z",
"country_code": "NG",
"label": "USDC Operating Wallet",
"destination_address": null,
"destination_chain": null
},
"meta": {
"request_id": "req_01HXYZ4K5ABCDEFGHJKLMNPQRS",
"timestamp": "2026-05-14T15:43:55.732Z",
"version": "1"
}
}{
"status": false,
"message": "Resource not found",
"code": "not_found",
"data": null,
"meta": {
"next_steps": {
"action": "Verify the resource identifier and retry.",
"docs_url": "https://docs.globalstack.io/errors#not_found"
},
"request_id": "req_01HXYZ4K5ABCDEFGHJKLMNPQRS",
"timestamp": "2026-05-14T15:43:55.732Z",
"version": "1"
}
}{
"status": false,
"message": "Resource not found",
"code": "not_found",
"data": null,
"meta": {
"next_steps": {
"action": "Verify the resource identifier and retry.",
"docs_url": "https://docs.globalstack.io/errors#not_found"
},
"request_id": "req_01HXYZ4K5ABCDEFGHJKLMNPQRS",
"timestamp": "2026-05-14T15:43:55.732Z",
"version": "1"
}
}{
"status": false,
"message": "Resource not found",
"code": "not_found",
"data": null,
"meta": {
"next_steps": {
"action": "Verify the resource identifier and retry.",
"docs_url": "https://docs.globalstack.io/errors#not_found"
},
"request_id": "req_01HXYZ4K5ABCDEFGHJKLMNPQRS",
"timestamp": "2026-05-14T15:43:55.732Z",
"version": "1"
}
}{
"status": false,
"message": "Resource not found",
"code": "not_found",
"data": null,
"meta": {
"next_steps": {
"action": "Verify the resource identifier and retry.",
"docs_url": "https://docs.globalstack.io/errors#not_found"
},
"request_id": "req_01HXYZ4K5ABCDEFGHJKLMNPQRS",
"timestamp": "2026-05-14T15:43:55.732Z",
"version": "1"
}
}Authorizations
API key issued during merchant onboarding.
Headers
Optional client-supplied key. Identical key + identical body within 24h replays the original response. Identical key + different body returns 409 idempotency_conflict. The hash is over raw bytes — clients retrying must send the byte-identical body; a re-serialised JSON payload will produce a different hash and a 409. Strongly recommended for retry-safe clients.
^[A-Za-z0-9_\-]{8,255}$Body
CreateWalletDto
^[A-Z0-9]{2,8}$^[A-Z]{2}$custodial, forwarding 128solana, base, ethereum 255Response
Wallet created successfully