Wallet Keys
Fetch the protectedSymmetricKey needed for wallet payouts.
POST /api/wallet/keys
Returns the selected wallet's walletId, protectedSymmetricKey and the owner's email. Use that wallet's key in transfers and payouts.
Cache keys per walletId, together with the owner's email. Refetch after that wallet's PIN-protected key changes.
Requires the
WALLET_PAYOUT_APIfeature flag. Without it the endpoint responds403.
Create a wallet with the platform PIN
The account holder first sets a six-digit wallet PIN on the Hodle platform. Wallet creation through the API requires that same walletPin. The server verifies it by unlocking an existing protected wallet belonging to the owner, then creates fresh wallet keys encrypted under that PIN. The PIN is not stored in plaintext.
{
"network": "polygon",
"walletPin": "123456",
"protectedSymmetricKey": "<existing owner wallet protectedSymmetricKey>"
}protectedSymmetricKey is optional on creation: it selects the existing owner wallet used to verify the PIN. If omitted, the server selects a protected reference wallet. It is not a replacement for the PIN, and a key from another owner is rejected.
The response contains data.id, data.address, data.network, data.balance and the new wallet's data.protectedSymmetricKey. EVM responses also include data.chainId. Save the returned id as walletId. Each successful creation creates a new wallet, including successive calls for the same network; keep its ID instead of calling create again to retrieve it.
- Missing or malformed
walletPin:400, with no wallet created. - No platform PIN established:
400. In sandbox, the first protected EVM wallet can be established with the supplied PIN. - Wrong platform PIN:
403. Three failures lock further attempts for 24 hours (429). - Supported creation networks:
polygon,base,solana. Solana keys are encrypted with the wallet's symmetric key. Solana creation is unavailable in sandbox; EVM creation uses Base Sepolia (chainId: 84532).
For a customer wallet, also pass its subAccountId. A reference subaccount inherits its owner's PIN; a legacy subaccount with its own user uses that user's PIN and email.
Reference the new wallet
{ "walletId": "6a4d263d4cc67674bf72a3c9" }Pass the same walletId in /api/wallet/transfer or /api/wallet/payout, alongside walletPin and the selected wallet's protectedSymmetricKey. For subaccount wallets also supply fromSubAccountId on transfers, or subAccountId on reads and payouts. The wallet must belong to that owner and scope. A missing, removed or foreign wallet returns an error; it never falls back to another wallet.
Without walletId, existing default selection applies. In sandbox, an explicit walletId selects the PIN-protected Safe on Base Sepolia for transfers and payout debits. The sandbox mints test tokens and transfers them from that Safe in one sponsored UserOperation; only the PIX leg is simulated. Without walletId, the existing application test wallet remains the settlement source.
For the signature-based payout flow, send walletId when preparing the payout. The returned payoutIntentId binds the wallet; omit walletId when submitting the signature.
PIN format
Wallet creation requires exactly six digits. Use the same platform PIN to unlock the resulting wallet for transfers and payouts.
Rate limit
1 request per minute per API key. Hitting the limit responds 429 with a Retry-After header.
Request
The API key identifies the owner. Send walletId to select a wallet and subAccountId when it belongs to a customer subaccount. Both fields are also accepted as query parameters. An empty body selects the default wallet.
curl --request POST \
--url https://api.hodle.com.br/api/wallet/keys \
--header "Authorization: Bearer $API_KEY" \
--header "Accept: application/json"const res = await fetch('https://api.hodle.com.br/api/wallet/keys', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.HODLE_API_KEY}`,
Accept: 'application/json',
},
})
const data = await res.json()import os, requests
res = requests.post(
"https://api.hodle.com.br/api/wallet/keys",
headers={
"Authorization": f"Bearer {os.environ['HODLE_API_KEY']}",
"Accept": "application/json",
},
)
data = res.json()Response
{
"success": true,
"data": {
"walletId": "6a4d263d4cc67674bf72a3c9",
"protectedSymmetricKey": "AoofiKHyVRLvdrknnXzoIh1Gd1YTwLaOBn4ibm103a4dpwHZA36dU9DiiZdDvQmNn...",
"email": "user@example.com"
}
}| Field | Type | Description |
|---|---|---|
data.walletId | string | ID of the wallet whose key is returned. |
data.protectedSymmetricKey | string | Pass this in POST /api/wallet/payout body. |
data.email | string | The user's email — keep alongside the key in your records. |
Errors
{ "success": false, "error": "WALLET_PAYOUT_API feature flag is not enabled for this user" }{ "success": false, "error": "Wallet not found" }{ "success": false, "error": "Wallet has no protected symmetric key" }This wallet has no usable PIN-protected key. Select a protected wallet by
walletIdor contact support for recovery of existing funds.
{ "success": false, "error": "Too many requests. Retry in 47 seconds" }When to refetch
Almost never. Re-fetch only if:
- You don't have the value cached yet for this wallet.
- A previous payout returned
Invalid PINdespite the user typing correctly — could mean the user reset their PIN and your cache is stale.