Skip to main content

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

  1. Claim: Agent polls and receives transaction with lease
  2. Execute: Agent has leaseDurationMs to sign and report
  3. Expire: If not reported, lease expires and transaction becomes available again
  4. Prevent Duplicates: Only one agent can hold a lease at a time
Custody ProviderRecommended LeaseRationale
Tatum KMS30 secondsFast API signing
Fireblocks60 secondsPolicy engine delays
BitGo60 secondsMulti-sig coordination
Custom30-60 secondsBased 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.