automationprivate-apiharintegrationsagentsclaude

Private API Automation: HAR-to-Tool for Stable Agent Integrations (No Browser Automation)

nNode Team6 min read

Browser automation works—until it doesn’t. If you’re building Claude Skills or client automations, the fastest path to reliable execution is often private API automation: call the same JSON endpoints the web app calls, instead of driving the DOM.

This post is a practical playbook you can run in under an hour: HAR → replay → adapter → tool contract → guardrails. It’s the same “API-first, token-in token-out” philosophy we’re building toward at nNode—because agents are far more deterministic when you give them structured inputs/outputs instead of HTML.

Why private API automation beats browser automation (most days)

Playwright/Puppeteer failures usually look like:

  • Selectors change (A/B tests, redesigns, “minor” UI updates)
  • Anti-bot / challenge pages appear
  • Timing flakes (spinners, lazy-loading, race conditions)
  • Token burn: the agent has to “see” and reason over lots of DOM

Meanwhile, many web apps run on stable XHR/fetch calls (or a single GraphQL endpoint). If you can capture and replay those requests, you can get:

  • Faster runs (no rendering)
  • More deterministic I/O (JSON)
  • Easier retries + idempotency
  • Cleaner audit logs

The 15-minute checklist: is this a good private API automation target?

Before you invest, confirm:

  1. Network tab shows XHR/fetch with JSON responses.
  2. Stable identifiers exist (record IDs, emails, slugs).
  3. Auth is reproducible (cookie session, bearer token, or SSO you can refresh).
  4. Write actions are “safe-able” (you can add approvals, idempotency keys, dedupe).
  5. Rate limits / abuse controls are understood (expect 429s; plan backoff).

If the app is purely server-rendered HTML with no JSON calls, stop—you’ll likely need browser automation.

Step 1 — Capture a clean HAR (and filter out the noise)

Record one “golden path” flow (e.g., Search → Open record → Create → Confirm), then export a HAR.

HAR hygiene tips:

  • Filter to fetch / xhr (ignore images, JS bundles, fonts)
  • Re-run the flow once to confirm which calls are essential
  • Identify the smallest set of requests that produces the outcome

If you want a quick way to list candidate endpoints from a HAR:

// har-list-endpoints.mjs
import fs from "node:fs";

const har = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
const entries = har.log.entries;

const endpoints = entries
  .filter(e => ["xhr", "fetch"].includes(e._resourceType))
  .map(e => ({
    method: e.request.method,
    url: e.request.url.split("?")[0],
    status: e.response.status,
    mime: e.response.content.mimeType
  }));

console.table(endpoints);

Run:

node har-list-endpoints.mjs ./capture.har

Step 2 — Find the “contract endpoints” (read path + write path)

For agent integrations, you usually want two categories:

  • Read path: list/search, get-by-id, lookup-by-email, etc.
  • Write path: create, update, action (approve, send, archive)

If it’s GraphQL

GraphQL often uses one URL (e.g., /graphql). Stability comes from:

  • operationName
  • variables
  • (sometimes) persisted queries

Your adapter should treat GraphQL like a set of named operations instead of “one endpoint.”

Step 3 — Make auth boring (cookies, headers, refresh)

Auth is where “it worked on my laptop” automations go to die.

Common patterns:

  • Session cookies: easiest to capture, but you need a re-login strategy.
  • Bearer tokens: often stored in localStorage; can expire; refresh token flow may exist.
  • CSRF tokens: sent as a header or cookie pair; must be preserved.

Practical approach for agencies:

  • Start with cookie-based replay (fastest to validate).
  • Then add a refresh step (programmatic login or “human reauth” fallback).
  • Store secrets in a vault; never commit HARs to git (they frequently contain tokens).

Step 4 — Replay outside the browser (curl → minimal client)

Take one essential request and reproduce it with curl. Your goal: one command that works consistently.

curl 'https://app.example.com/api/v1/items' \
  -H 'accept: application/json' \
  -H 'content-type: application/json' \
  -H 'x-csrf-token: <csrf>' \
  -H 'cookie: session=<session_cookie>' \
  --data '{"query":"invoices","page":1}'

Once curl works, wrap it in a tiny adapter.

Step 5 — Normalize into a tool contract (inputs/outputs your agent can trust)

This is where private API automation becomes “agent-ready.” Don’t expose raw app payloads—define a stable contract.

Example tool schema (what you want your agent to see):

{
  "name": "search_invoices",
  "description": "Search invoices in ExampleApp by customer email and status.",
  "input_schema": {
    "type": "object",
    "properties": {
      "customer_email": {"type": "string"},
      "status": {"type": "string", "enum": ["open", "paid", "void"]},
      "page": {"type": "integer", "minimum": 1, "default": 1}
    },
    "required": ["customer_email"]
  },
  "output_schema": {
    "type": "object",
    "properties": {
      "invoices": {
        "type": "array",
        "items": {
          "type": "object",
          "properties": {
            "invoice_id": {"type": "string"},
            "amount": {"type": "number"},
            "currency": {"type": "string"},
            "status": {"type": "string"}
          },
          "required": ["invoice_id", "status"]
        }
      },
      "next_page": {"type": ["integer", "null"]}
    },
    "required": ["invoices"]
  }
}

Key idea: your agent shouldn’t care whether the underlying system is REST, GraphQL, or “weird private JSON.”

Step 6 — Production hardening: retries, idempotency, safe re-runs

Automations fail. Your job is to make failures predictable.

Retries with backoff + jitter

function sleep(ms: number) {
  return new Promise(r => setTimeout(r, ms));
}

async function withRetry<T>(fn: () => Promise<T>, max = 4) {
  for (let attempt = 0; attempt <= max; attempt++) {
    try {
      return await fn();
    } catch (err: any) {
      const retryable = [429, 500, 502, 503, 504].includes(err?.status);
      if (!retryable || attempt === max) throw err;
      const base = 500 * 2 ** attempt;
      const jitter = Math.floor(Math.random() * 200);
      await sleep(base + jitter);
    }
  }
  throw new Error("unreachable");
}

Idempotency keys for writes

For POST actions that create side effects, add a client-generated idempotency key if the API supports it (common header name: Idempotency-Key). If it doesn’t, implement a dedupe layer on your side (e.g., request fingerprint → “already processed” record).

Step 7 — Safety & governance (agency-ready)

This is where nNode’s “guardrails and control” mindset matters in real deployments.

Add:

  • Allowlist of domains + endpoints (block everything else)
  • Approval gates for writes (POST/PATCH/DELETE)
  • Redaction (strip cookies/tokens from logs; store only metadata)
  • Audit logs with request IDs and business context (“who approved what”)

A simple but powerful pattern is “approve on diff”: show the human the normalized tool input before executing the write.

Maintenance plan: detect breaking changes early

Private APIs change—but usually in ways you can monitor.

  • Write contract tests that run daily (or per deploy)
  • Version your adapter (v1, v2) instead of hot-editing
  • Alert on error rates and auth failures (auth issues look like sudden 401/403 spikes)

Example workflow decomposition (template)

Here’s a generic, repeatable pattern:

  1. Search (search_* tool) → returns IDs
  2. Enrich (get_*_by_id) → returns normalized fields
  3. Write (create_* / update_*) → requires approval + idempotency
  4. Notify (Slack/Email) → includes audit trail

This separation is what makes API-first agents faster and safer than “one giant browser script.”


Want this as a repeatable system, not a one-off script?

If you’re an automation agency or building Claude-powered workflows for internal teams, nNode is designed around API-first execution—turning real app requests into stable, auditable tools with guardrails so you can ship automations that survive UI churn.

When you’re ready, take a look at nnode.ai and try mapping one of your most brittle browser flows into a HAR-to-tool integration.

Build your first AI Agent today

Join the waiting list for nNode and start automating your workflows with natural language.

Get Started