Agent Endpoints
APIs for custody agents to poll for pending work and report execution results.
These endpoints use API key authentication (X-API-Key header) instead of JWT tokens.
Transaction Polling
POST /api/agent/transactions/poll
Poll for pending transactions that need to be signed and executed.
Headers:
X-API-Key: rfk_live_xxxxxxxxxxxxx
Content-Type: application/json
Request Body:
{
"leaseDurationMs": 30000
}
Response (Transaction Available):
{
"success": true,
"data": {
"transaction": {
"id": 4001,
"operationId": 3001,
"unsignedTx": "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAQAHEAoMCg...",
"metadata": {
"blockchain": "solana",
"walletId": 456,
"amount": "10000.00",
"token": "USDC",
"type": "SUPPLY"
},
"claimedAt": "2025-10-23T14:00:00Z",
"leaseExpiresAt": "2025-10-23T14:00:30Z"
}
}
}
Response (No Transactions):
{
"success": true,
"data": {
"transaction": null
}
}
The leaseDurationMs parameter claims the transaction for the specified duration. If not reported within this time, the lease expires and the transaction becomes available again.
POST /api/agent/transactions/:id/report
Report the execution status of a transaction.
Headers:
X-API-Key: rfk_live_xxxxxxxxxxxxx
Content-Type: application/json
Request Body (Success):
{
"status": "SUBMITTED",
"txHash": "5aApzp1XxiNzFotAiZt6z7BaTTYCS7964ycpxwYp4eVyxentgDcevD12pKoT7qHUakoBnp3nZPqrXz4479FZs59J"
}
Request Body (Failure):
{
"status": "FAILED",
"error": "Insufficient SOL for transaction fees"
}
Response:
{
"success": true,
"data": {
"id": 4001,
"status": "SUBMITTED",
"txHash": "5aApzp1XxiNzFotAiZt6z7BaTTYCS7964ycpxwYp4eVy...",
"reportedAt": "2025-10-23T14:00:15Z"
}
}
Action Polling
POST /api/agent/actions/poll
Poll for pending custody actions (wallet creation, etc.).
Headers:
X-API-Key: rfk_live_xxxxxxxxxxxxx
Content-Type: application/json
Request Body:
{
"leaseDurationMs": 30000
}
Response (Action Available):
{
"success": true,
"data": {
"action": {
"id": 5001,
"type": "CREATE_WALLET",
"payload": {
"blockchain": "solana",
"label": "New Treasury Wallet"
},
"status": "PENDING",
"createdAt": "2025-10-23T14:00:00Z",
"leaseExpiresAt": "2025-10-23T14:00:30Z"
}
}
}
Response (No Actions):
{
"success": true,
"data": {
"action": null
}
}
POST /api/agent/actions/:id/report
Report the outcome of an action.
Headers:
X-API-Key: rfk_live_xxxxxxxxxxxxx
Content-Type: application/json
Request Body (Success):
{
"status": "SUCCESS",
"result": {
"address": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
"signatureId": "wallet_123"
}
}
Request Body (Failure):
{
"status": "FAILED",
"error": "Custody provider API unavailable"
}
Response:
{
"success": true,
"data": {
"action": {
"id": 5001,
"type": "CREATE_WALLET",
"status": "SUCCESS",
"result": {
"address": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU"
}
}
}
}
Agent Implementation
Basic Agent Loop
import axios from 'axios';
const agent = axios.create({
baseURL: process.env.REBELFI_BASE_URL,
headers: { 'X-API-Key': process.env.REBELFI_API_KEY }
});
async function pollAndExecute() {
while (true) {
try {
// Poll for transaction
const { data } = await agent.post('/api/agent/transactions/poll', {
leaseDurationMs: 30000
});
if (data.data.transaction) {
const tx = data.data.transaction;
console.log(`Claimed transaction ${tx.id}`);
// Sign transaction with custody solution
const signedTx = await custodySolution.sign(tx.unsignedTx);
// Submit to blockchain
const txHash = await blockchain.submit(signedTx);
// Report success
await agent.post(`/api/agent/transactions/${tx.id}/report`, {
status: 'SUBMITTED',
txHash: txHash
});
console.log(`Transaction ${tx.id} submitted: ${txHash}`);
}
} catch (error) {
console.error('Agent error:', error);
}
// Wait before next poll
await new Promise(resolve => setTimeout(resolve, 10000));
}
}
pollAndExecute();
Error Handling
async function pollWithRetry(maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const { data } = await agent.post('/api/agent/transactions/poll', {
leaseDurationMs: 30000
});
if (data.data.transaction) {
return await executeTransaction(data.data.transaction);
}
return null;
} catch (error) {
if (attempt === maxRetries - 1) {
console.error('Max retries exceeded:', error);
throw error;
}
// Exponential backoff
const delay = Math.pow(2, attempt) * 1000;
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
async function executeTransaction(tx) {
try {
// Sign transaction
const signedTx = await custodySolution.sign(tx.unsignedTx);
// Submit to blockchain
const txHash = await blockchain.submit(signedTx);
// Report success
await agent.post(`/api/agent/transactions/${tx.id}/report`, {
status: 'SUBMITTED',
txHash: txHash
});
return { success: true, txHash };
} catch (error) {
// Report failure
await agent.post(`/api/agent/transactions/${tx.id}/report`, {
status: 'FAILED',
error: error.message
});
return { success: false, error: error.message };
}
}
Lease Management
How Leases Work
- Claim: Agent polls and receives transaction with lease
- Execute: Agent has
leaseDurationMs to sign and report
- Expire: If not reported, lease expires and transaction becomes available again
- Prevent Duplicates: Only one agent can hold a lease at a time
Recommended Lease Duration
| Custody Provider | Recommended Lease | Rationale |
|---|
| Tatum KMS | 30 seconds | Fast API signing |
| Fireblocks | 60 seconds | Policy engine delays |
| BitGo | 60 seconds | Multi-sig coordination |
| Custom | 30-60 seconds | Based on signing latency |
If your lease expires before reporting, another agent may claim the transaction, potentially causing duplicate submissions. Set lease duration > signing time + network latency.
Monitoring Agent Health
// Track agent metrics
const metrics = {
pollCount: 0,
successCount: 0,
failureCount: 0,
lastPollAt: null,
lastExecutionAt: null
};
async function pollWithMetrics() {
metrics.pollCount++;
metrics.lastPollAt = new Date();
const { data } = await agent.post('/api/agent/transactions/poll', {
leaseDurationMs: 30000
});
if (data.data.transaction) {
try {
await executeTransaction(data.data.transaction);
metrics.successCount++;
metrics.lastExecutionAt = new Date();
} catch (error) {
metrics.failureCount++;
}
}
}
// Expose health endpoint
app.get('/health', (req, res) => {
const isHealthy =
metrics.lastPollAt &&
(Date.now() - metrics.lastPollAt.getTime()) < 60000; // Polled in last minute
res.json({
status: isHealthy ? 'healthy' : 'unhealthy',
metrics: {
...metrics,
successRate: (metrics.successCount / (metrics.successCount + metrics.failureCount) * 100).toFixed(2) + '%'
}
});
});
Rate Limits
Agent endpoints have higher rate limits to support frequent polling:
- POST /api/agent/transactions/poll: 1000 requests/minute
- POST /api/agent/transactions/:id/report: 500 requests/minute
- POST /api/agent/actions/poll: 1000 requests/minute
- POST /api/agent/actions/:id/report: 500 requests/minute
Poll every 10-30 seconds for optimal balance between responsiveness and API usage.