Skip to main content

Approvals

When a transaction value exceeds the auto-approve-threshold policy, Steward queues it for human review instead of signing immediately. The approvals API lets you list, inspect, approve, and deny these pending transactions. Base path: /approvals
Auth: Tenant-level (X-Steward-Key)

How Approvals Work

  1. Agent submits a sign request via SDK or REST API
  2. Policy engine evaluates all rules — hard policies (spending-limit, etc.) must pass
  3. If auto-approve-threshold is set and the value exceeds it, the transaction is queued (HTTP 202)
  4. Steward fires a tx.pending webhook event (if configured)
  5. A human reviews via the API, SDK, or <ApprovalQueue> React component
  6. On approval: Steward signs the transaction and optionally broadcasts
  7. On denial: the transaction is marked rejected and a tx.denied webhook fires

List Pending Approvals

GET /approvals?status=pending&limit=50&offset=0
curl https://api.steward.fi/approvals \
  -H "X-Steward-Key: your-tenant-key"
Query params:
ParamDefaultDescription
statuspendingFilter by status: pending, approved, rejected, or all
limit50Max results (max: 200)
offset0Pagination offset
Response:
{
  "ok": true,
  "data": [
    {
      "id": "appr_550e8400-...",
      "txId": "tx_660f9500-...",
      "agentId": "trading-bot-1",
      "agentName": "Trading Bot Alpha",
      "status": "pending",
      "requestedAt": "2026-03-27T14:00:00Z",
      "resolvedAt": null,
      "resolvedBy": null,
      "toAddress": "0xUniswapRouter...",
      "value": "2000000000000000000",
      "chainId": 8453,
      "txStatus": "pending_approval"
    }
  ]
}

Approval Stats

GET /approvals/stats
Returns aggregate counts across all agents in the tenant:
{
  "ok": true,
  "data": {
    "pending": 3,
    "approved": 142,
    "rejected": 8,
    "total": 153,
    "avgWaitSeconds": 340
  }
}

Approve a Transaction

POST /approvals/:id/approve
curl -X POST https://api.steward.fi/approvals/appr_550e8400-.../approve \
  -H "X-Steward-Key: your-tenant-key"
Steward immediately signs the transaction. If broadcast: true was set in the original sign request, the transaction is also broadcast to the chain. Response:
{
  "ok": true,
  "data": {
    "approvalId": "appr_550e8400-...",
    "status": "approved",
    "txHash": "0xabc123...",
    "resolvedAt": "2026-03-27T14:05:00Z"
  }
}

Deny a Transaction

POST /approvals/:id/deny
curl -X POST https://api.steward.fi/approvals/appr_550e8400-.../deny \
  -H "X-Steward-Key: your-tenant-key" \
  -H "Content-Type: application/json" \
  -d '{ "reason": "Amount too large for current market conditions" }'
The reason field is optional but recommended — it’s included in the tx.denied webhook payload. Response:
{
  "ok": true,
  "data": {
    "approvalId": "appr_550e8400-...",
    "status": "rejected",
    "resolvedAt": "2026-03-27T14:05:00Z",
    "resolvedBy": "manual"
  }
}

Auto-Expiry

Approvals auto-expire after the duration set in the tenant’s approvalConfig.autoExpireSeconds. Once expired, the transaction cannot be approved and the agent must resubmit. Default: 86400 (24 hours). Set to 0 to disable auto-expiry.

Setting Up the Auto-Approve Threshold

Configure the threshold in your agent’s policy set:
await client.setPolicies("trading-bot-1", [
  // Other policies...
  {
    id: "auto-threshold",
    type: "auto-approve-threshold",
    enabled: true,
    config: {
      threshold: "10000000000000000", // 0.01 ETH — auto-sign below, queue above
    },
  },
]);
Transactions at or below the threshold are signed immediately. Transactions above the threshold are queued.
The auto-approve-threshold policy is a soft gate — it never hard-rejects. But hard policies (spending-limit, approved-addresses, etc.) are still evaluated first. A transaction can be both blocked by a hard policy AND above the threshold; it will be rejected, not queued.

Using the React Component

The <ApprovalQueue> component from @stwd/react provides a ready-made UI for reviewing pending transactions:
import { StewardProvider, ApprovalQueue } from "@stwd/react";

function AdminDashboard({ agentId }) {
  return (
    <StewardProvider client={client} agentId={agentId}>
      <ApprovalQueue
        pollInterval={5000}
        onApprove={(id) => console.log("Approved:", id)}
        onDeny={(id, reason) => console.log("Denied:", id, reason)}
      />
    </StewardProvider>
  );
}

Webhook Integration

Configure a tx.pending webhook to get notified when transactions need review:
// Register webhook
await fetch("https://api.steward.fi/webhooks", {
  method: "POST",
  headers: { "X-Steward-Key": apiKey, "Content-Type": "application/json" },
  body: JSON.stringify({
    url: "https://your-app.com/webhooks/steward",
    events: ["tx.pending"],
  }),
});
Your webhook handler receives:
{
  "event": "tx.pending",
  "agentId": "trading-bot-1",
  "data": {
    "approvalId": "appr_...",
    "to": "0x...",
    "value": "2000000000000000000",
    "chainId": 8453
  }
}
Then approve or deny via the REST API from your backend.