Create Transfer
Transfer points to another member or create pending gift transfer
/members/me/transfersOverview
Transfer points to another member directly (using their member external ID) or create a pending gift transfer (using email or phone). Pending transfers can be claimed by the recipient later when they join or verify their contact information.
Authentication
Requires a valid Clerk JWT token in the Authorization header.
Security
Reverification Required: This endpoint requires reverification if the user hasn't verified their credentials within the last 10 minutes. This is a sensitive action involving point transfers.
Request Body
toMemberExternalIdbodystringRecipient member external ID (ULID) for existing member transfer. Must be exactly 26 characters.
recipientEmailbodystringRecipient email for gift transfer (pending). Valid email format required.
recipientPhonebodystringRecipient phone for gift transfer (pending). Must be valid E.164 format (e.g., +1234567890).
amountbodynumberrequiredAmount of points to transfer. Must be at least 1 point and within your transfer limits.
notebodystringOptional note/message for the transfer. Maximum 500 characters.
Note: Either toMemberExternalId OR (recipientEmail or recipientPhone) must be provided, but not both.
Request
curl -X POST "http://localhost:3000/v1/members/me/transfers" \
-H "Authorization: Bearer YOUR_CLERK_JWT" \
-H "Content-Type: application/json" \
-d '{
"toMemberExternalId": "01ARZ3NDEKTSV4RRFFQ69G5FAV",
"amount": 1000,
"note": "Happy birthday!"
}'Response
transferIdstringrequiredTransfer ID (correlation ID) for tracking the transfer
correlationIdstringrequiredCorrelation ID for the transfer transaction (same as transferId)
amountnumberrequiredAmount transferred in points
statusstringrequiredTransfer status: "COMPLETED" for direct transfers, "PENDING" for gift transfers
expiresAtstringExpiration date for pending transfers (ISO 8601 format). Only present for gift transfers.
Response
{
"transferId": "018f1234-5678-9abc-def0-123456789abc",
"correlationId": "018f1234-5678-9abc-def0-123456789abc",
"amount": 1000,
"status": "COMPLETED"
}Transfer Types
Direct Transfer (Member-to-Member)
- Recipient: Existing member identified by external ID
- Status: COMPLETED immediately
- Points: Transferred instantly to recipient's account
- Use Case: Send points to known members
Gift Transfer (Pending)
- Recipient: Email or phone number (not yet a member)
- Status: PENDING until claimed
- Expiration: 30 days from creation
- Points: Held in escrow until claimed or expired
- Use Case: Gift points to friends who aren't members yet
Validation Rules
Amount Limits
- Minimum: 1 point
- Maximum: Based on your tier and transfer limits
- Check: Use Get Transfer Limits to verify
Recipient Validation
- Member External ID: Must be valid 26-character ULID
- Email: Must be valid email format
- Phone: Must be valid E.164 format
- Self Transfer: Cannot transfer to yourself
Balance Check
- Sufficient Balance: Must have enough VALID points
- Real-time: Balance checked at transfer time
- Type: Only VALID points can be transferred (not CREDIT points)
Error Handling
Common Errors
| Error Code | Description | Resolution |
|---|---|---|
INSUFFICIENT_BALANCE | Not enough points | Check wallet balance |
SELF_TRANSFER | Trying to send to self | Use different recipient |
TRANSFER_LIMIT_EXCEEDED | Exceeds daily/monthly limits | Wait or contact support |
TRANSFER_FAILED | Generic transfer failure | Retry or contact support |
Rate Limiting
- No specific rate limit for transfers
- Security measures in place for fraud prevention
- Reverification required for sensitive actions
Use Cases
Send Birthday Gift
const sendBirthdayGift = async (recipientEmail: string, amount: number) => {
try {
const response = await fetch('/api/members/me/transfers', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
recipientEmail,
amount,
note: '🎂 Happy Birthday! Enjoy some points on me!'
})
});
const result = await response.json();
if (response.ok) {
console.log(`Gift sent! Transfer ID: ${result.transferId}`);
console.log(`Status: ${result.status}`);
if (result.expiresAt) {
console.log(`Expires: ${result.expiresAt}`);
}
} else {
handleTransferError(result);
}
} catch (error) {
console.error('Failed to send gift:', error);
}
};Transfer to Known Member
const transferToMember = async (memberExternalId: string, amount: number, note: string) => {
try {
const response = await fetch('/api/members/me/transfers', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
toMemberExternalId: memberExternalId,
amount,
note
})
});
const result = await response.json();
if (response.ok && result.status === 'COMPLETED') {
console.log(`Transfer completed! ${amount} points sent.`);
}
} catch (error) {
console.error('Transfer failed:', error);
}
};Bulk Gift Campaign
const sendBulkGifts = async (recipients: Array<{email: string, amount: number}>) => {
const results = await Promise.allSettled(
recipients.map(({email, amount}) =>
fetch('/api/members/me/transfers', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
recipientEmail: email,
amount,
note: 'Thank you for being an amazing customer!'
})
})
)
);
const successful = results.filter(r => r.status === 'fulfilled').length;
console.log(`${successful}/${recipients.length} gifts sent successfully`);
};Security Notes
Reverification
- Required: For all transfer operations
- Timeframe: Within last 10 minutes
- Methods: SMS OTP or email verification
- Bypass: Not possible for security reasons
Fraud Prevention
- Monitoring: Unusual transfer patterns detected
- Limits: Daily and monthly transfer limits enforced
- Validation: Recipient verification for suspicious activity
Related Endpoints
- Get Transfer Limits - Check your transfer limits before sending
- Member Lookup - Find member details before transferring
- List Pending Transfers - View your pending transfers
- Claim Pending Transfer - Claim gifts sent to you