Skip to main content

The problem

An AI agent with admin API access can make destructive, hard-to-reverse changes the moment it decides to. Deactivating the wrong user account, escalating permissions incorrectly, or deleting data silently can cause real harm before anyone notices.
// ❌ Direct admin call — executes immediately, no review, no record
await fetch(`https://api.yourapp.com/admin/users/${userId}/deactivate`, {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.ADMIN_API_KEY}` },
});
There’s no approval gate, no notification, and no audit trail showing who authorized the action.

The SafeFetch solution

Wrap the admin call in safeFetch() with approve: true and a callback URL. The action is held as awaiting_approval until a human approves it. Your backend is notified when the action reaches a terminal status.

Full example

import { safeFetch } from "safefetch";

const action = await safeFetch({
  url: `https://api.yourapp.com/admin/users/${userId}/deactivate`,
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.ADMIN_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: {
    reason: "Flagged for policy violation",
    initiated_by: "ai-agent",
  },
  approve: true,
  // Email a signed magic-link approve/reject button to the reviewer
  notify: { email: "security-team@yourcompany.com" },
  // POST to your backend when the action completes or is rejected
  callback: "https://yourapp.com/webhooks/safefetch",
  // Prevent duplicate deactivation requests for the same user
  dedupe: `deactivate-user:${userId}`,
});

console.log(action.id);     // act_...
console.log(action.status); // "awaiting_approval"
The action is stored immediately and returns with status: "awaiting_approval". Your admin API is not called until a reviewer approves.

What happens next

1

Reviewer receives email

If you passed notify.email, SafeFetch sends an email with one-click Approve and Reject buttons. Links are signed — no login required.
2

Reviewer approves

Clicking Approve moves the action to pending and dispatches the request to your admin API. The receipt page shows the full details: who approved, when, and what your API returned.
3

Your callback fires

SafeFetch POSTs to your callback URL when the action reaches a terminal status (completed, failed, or cancelled). Check action.status and action.response_code to confirm the outcome and update your records.

Handling the callback

Verify the result in your webhook handler and take follow-up action:
import type { Action } from "safefetch";

// In your webhook handler (e.g., Express, Hono):
app.post("/webhooks/safefetch", async (req, res) => {
  const action: Action = req.body;

  if (action.status === "completed" && action.response_code === 200) {
    // Admin action succeeded — log it and notify stakeholders
    await db.auditLog.insert({
      action_id: action.id,
      type: "user_deactivated",
      user_id: action.body?.userId,
      completed_at: action.completed_at,
    });
    await notifyStakeholders(`User ${action.body?.userId} has been deactivated.`);
  } else if (action.status === "cancelled") {
    // Reviewer rejected — record the decision
    await db.auditLog.insert({
      action_id: action.id,
      type: "deactivation_rejected",
      user_id: action.body?.userId,
    });
  } else if (action.status === "failed") {
    await alertTeam(`Admin action failed: ${action.last_error}`);
  }

  res.sendStatus(200);
});

Checking status programmatically

Poll or inspect the action at any point using its ID:
const action = await safeFetch.get("act_Xk9mP2nQ4rT1vW8sY");

if (action.status === "awaiting_approval") {
  console.log("Pending reviewer sign-off.");
} else if (action.status === "completed") {
  console.log("Action completed. API responded:", action.response_code);
} else if (action.status === "cancelled") {
  console.log("Action was rejected by reviewer.");
} else if (action.status === "failed") {
  console.log("Action failed:", action.last_error);
}

Approving or rejecting via API

If you have your own internal approval UI, skip the magic-link email and handle approval programmatically:
// Approve
await safeFetch.approve("act_Xk9mP2nQ4rT1vW8sY");

// Reject (cancel)
await safeFetch.cancel("act_Xk9mP2nQ4rT1vW8sY");

Receipt page as audit trail

Every action has a receipt page in the dashboard. It shows the full audit trail: who approved it, when, what your API returned, and how long delivery took. Share the URL with your security or compliance team.
https://api.safefetch.dev/dashboard/actions/act_Xk9mP2nQ4rT1vW8sY
For sensitive operations, store the action ID in your own database alongside the event. You can always retrieve the full record via safeFetch.get(action.id) or the API.
approve: true cannot be combined with sync: true. Synchronous mode waits for immediate completion — that’s incompatible with an asynchronous human decision.