Four API Integration Patterns I Reuse on Every Agent Deployment

Carson Rodrigues

Carson Rodrigues / January 25, 2026

6 min read––– views

fdeintegrations

Forward-deployed agent work has a rhythm to it. The model and the loop stay roughly the same from customer to customer; what changes is the tangle of APIs the agent has to live inside — their ticketing system, their billing platform, their in-house inventory service with the endpoint someone wrote in 2017. Building backends that served tens of thousands of locations taught me that the integration layer is where reliability is actually decided, and deploying agents on top of those integrations sharpened the lesson.

After enough deployments, four patterns emerged that I now reach for by default. None of them are novel. All of them are the difference between an integration you extend and an integration you fear.


Pattern 1: The adapter layer — agents never touch raw APIs

The tempting shortcut is to hand the model a thin tool that proxies straight to the third-party API: same parameters, same response, same errors. It works in the demo and decays from there, because now your prompts, your evals, and your model's learned behavior are all coupled to someone else's API design.

Instead, put an adapter between every external API and the agent:

  • The agent sees your canonical interface. get_customer(id) returns the same shape whether the data behind it is Salesforce, HubSpot, or a CSV-backed internal service. Swap the backend, keep the prompts and evals.
  • The adapter normalizes the weirdness. One API paginates with cursors, another with offsets; one returns snake_case, another XML. None of that belongs in the model's context window.
  • Errors get translated too (more on this in Pattern 3) — the agent should see one error vocabulary, not five vendors' worth.
  • Policy lives here. Field allowlists, write gates, redaction of fields the agent has no business reading. The adapter is your enforcement point, because the model's promises are not an enforcement point.

In NestJS this falls out naturally — an abstract CrmPort / TicketingPort interface, one injectable adapter per vendor, chosen per tenant at runtime. The agent's tools depend on the port, never the vendor. Same idea works in any stack; MCP servers make a natural packaging for the adapter layer if you want portability across agent frontends.


Pattern 2: Credential management is per-tenant, or it's wrong

Single-tenant thinking is how credentials end up in environment variables. The moment you deploy an agent for more than one customer, credentials become data, not config: every tenant brings its own API keys, OAuth grants, base URLs, and — fun one — API versions.

The setup that holds up:

  • A secrets store keyed by tenant (AWS Secrets Manager or SSM in my deployments), fetched at request time and cached briefly. Nothing tenant-specific baked into the deploy artifact.
  • Scoped, least-privilege credentials. Ask the customer for an integration user with exactly the permissions the tools need. This isn't just security hygiene — it's your safety net. An agent physically incapable of deleting records can't be talked into deleting records.
  • Rotation and revocation as first-class events. Customers rotate keys without telling you. Detect auth failures per tenant, alert on them separately from generic errors, and have a self-service path for the customer to update credentials that doesn't involve emailing a key (never let them do that — you'll spend the afternoon scrubbing it).
  • Hard tenant isolation in the execution path. The nightmare scenario in multi-tenant agent systems is a tool call running with the wrong tenant's credentials. Bind credentials to the request context at the entry point, pass them explicitly, and make it structurally impossible for tenant A's session to resolve tenant B's secrets. Test this. It's the one bug category where "rare" is not reassuring.

Pattern 3: Pagination, timeouts, and limits are agent-visible errors

Here's the mental shift specific to agent integrations: the caller of your API client is a language model, and it can only handle what it can see.

Traditional integration code hides mechanics — it auto-paginates, retries silently, times out into a generic exception. For an agent, that hiding is harmful. If a search tool silently returns page one of forty, the model concludes there were only 25 results and reasons from that. If a timeout surfaces as an empty list, the model tells the user "you have no orders." (I called this failure mode out in the production agents post; the fix lives in the integration layer.)

So the adapter returns structured, actionable truth:

{
  "results": [ ... 25 items ... ],
  "truncated": true,
  "total_available": 993,
  "hint": "Refine the query or request the next page with cursor=abc123."
}

And for failures:

{
  "error": "upstream_timeout",
  "retryable": true,
  "hint": "The inventory system did not respond within 10s. Retry once; if it fails again, tell the user the system is slow and offer to follow up."
}

Rules I hold to: never let "failed" look like "empty." Distinguish retryable from permanent errors so the model doesn't hammer a dead endpoint. Put remediation in the error — models are startlingly good at following a hint field. And cap what you hand back: dumping 400KB of paginated JSON into context is its own denial of service; summarize or window it at the adapter.

The result is an agent that says "I found 993 matching orders — want me to narrow by date?" instead of hallucinating a conclusion from a truncated list. Same model, better integration, dramatically better product.


Pattern 4: Contract tests against the customer's actual systems

Your integration doesn't run against the vendor's docs. It runs against this customer's instance — their custom fields, their permissions, their plugins, their API version from two springs ago. The docs are a rumor; the instance is the truth.

What I run on every deployment:

  • A recorded-fixture suite for fast feedback: captured (sanitized) real responses from the customer's system, replayed in CI. This pins down the shapes your adapter must handle without hammering their sandbox on every commit.
  • A live smoke suite at onboarding and on a schedule — a dozen read-only calls against their sandbox that assert the contract: expected fields present, types right, auth works, pagination behaves. Run it before go-live, then daily. When a customer's admin changes a permission or a vendor ships a breaking change, you want to find out from a red check, not from an agent confidently mis-answering for a week.
  • Schema-drift alarms. Cheap version: hash the field set of key responses and alert when it changes. New fields are usually fine; missing ones almost never are.
  • Eval cases wired to the contract. When a contract test finds a new edge (a null where the docs promised a string), it becomes both an adapter fix and an eval case for the agent. The integration suite and the agent's eval suite grow together — they're testing two layers of the same promise.

This is also, quietly, a customer-relationship tool. "Our nightly checks caught your API change before it affected anything" is the sentence that gets you renewed.


The takeaway

Adapters so the model never marries a vendor's API. Per-tenant credentials treated as data with hard isolation. Pagination and timeouts surfaced as structured, hint-bearing errors the model can act on. Contract tests against the customer's real instance, feeding the eval suite. Four patterns, every deployment, no exceptions — because in agent systems the model is the commodity and the integration layer is where the reliability actually lives.


Related reading

Available for senior AI / contract / FDE work

Building something with AI?

Voice agents, MCP servers, LLM pipelines, agentic workflows — pick a slot, drop a message, or send your email and I'll reply within a day.

or leave your email

Replies within ~24 hours · Remote-first · global · open to relocation