Connect EasyPost to AI Agents: Automate Customs & Address Validation
Give your AI agent EasyPost tools.
Connect EasyPost to AI agents to autonomously manage shipping labels, validate addresses, and process customs documentation. This guide shows how to fetch EasyPost tools via Truto and build resilient multi-step workflows.
In this guide
- 01Initialize Truto Tool Manager
- 02Fetch EasyPost Tools
- 03Bind Tools to LLM
- 04Implement Resilient Execution Loop
The guide
Learn how to connect EasyPost to AI agents using Truto's tools endpoint. Automate shipping, customs validation, and rate shopping with LangChain and CrewAI.
You want to connect EasyPost to an AI agent so your system can autonomously validate addresses, generate shipping labels, shop for carrier rates, and process international customs forms. Here is exactly how to do it using Truto's /tools endpoint and SDK, bypassing the need to build a custom logistics integration from scratch.
Giving a Large Language Model (LLM) read and write access to your logistics infrastructure introduces significant complexity. You are bridging non-deterministic AI logic with highly deterministic, immutable physical shipping processes. If your team uses ChatGPT, check out our guide on connecting EasyPost to ChatGPT, or if you are building on Anthropic's models, read our guide on connecting EasyPost to Claude. For developers building custom autonomous workflows, you need a programmatic way to fetch these tools and bind them to your agent framework.
This guide breaks down exactly how to fetch AI-ready tools for EasyPost, bind them natively to an LLM using LangChain (or any framework like LangGraph, CrewAI, or the Vercel AI SDK), and execute complex logistics workflows. For a broader look at the architecture behind this approach, refer to our research on architecting AI agents and the SaaS integration bottleneck.
The Engineering Reality of the EasyPost API
Giving an LLM access to external data sounds simple in a prototype. You write a standard Node.js fetch request and wrap it in a tool decorator. In production against a platform like EasyPost, standard REST assumptions collapse.
EasyPost is a transactional API designed for physical logistics. If you hardcode these interactions into your agent without understanding the domain, your agent will constantly hallucinate invalid requests, attempt impossible state changes, and bleed money on incorrect postage. Here are the specific architectural realities you must account for.
The Immutability Constraint
Standard LLMs expect APIs to behave like simple CRUD databases. If an agent creates a record and realizes it made a mistake, it naturally attempts a PATCH or PUT request to update it.
EasyPost does not allow this for core shipping primitives. Address objects and Parcel objects are strictly immutable after creation. If an agent creates a package with incorrect dimensions, it cannot update the existing Parcel ID. It must generate a completely new Parcel object. If your tool layer does not explicitly communicate this immutability via schema descriptions, the agent will get trapped in an endless loop of failed update_parcel requests.
Nested Customs Information Traps
International shipping via EasyPost requires heavily nested, highly validated JSON structures. To ship internationally, an agent cannot simply pass a destination address. It must create a CustomsItem for every physical product in the box, attach those items to a CustomsInfo object, specify the restriction type and certification, and then attach that CustomsInfo object to the parent Shipment.
If you expose the raw EasyPost endpoints directly to an LLM, the model struggles to sequence these dependencies correctly. It will frequently attempt to create a shipment with a raw array of items instead of the required CustomsInfo object ID, resulting in immediate API rejections.
Asynchronous Batch Operations
When processing hundreds of shipments, EasyPost uses Batch operations. Standard LLMs expect synchronous behavior - they call a function and expect the final state in the response. EasyPost's create_a_easy_post_batch and easy_post_batches_buy endpoints are asynchronous. They enqueue a background job and return a Batch object in a pending state.
An AI agent must be equipped with polling tools to check the status of a batch, or your system must implement a webhook listener that wakes the agent up when the batch generation completes. If you fail to design this state loop, the agent will assume a label is ready for print before the carrier has even returned a tracking number.
Rate Limiting and Client-Side Backoff
When your agent attempts to rate-shop across multiple carriers for dozens of orders simultaneously, it will hit rate limits. Truto passes upstream rate limit errors directly to the caller. When EasyPost returns an HTTP 429 Too Many Requests, Truto normalizes the upstream rate limit information into standard headers (ratelimit-limit, ratelimit-remaining, ratelimit-reset) per the IETF specification.
Truto does not retry, throttle, or apply backoff on rate limit errors. The caller - your agent framework - is entirely responsible for retry and backoff logic. Your system must intercept these 429s, read the ratelimit-reset header, pause execution, and retry. If you rely on Truto to automatically absorb these errors, your workflow will crash.
Fetching EasyPost Tools via Truto
Instead of manually coding wrappers for every EasyPost endpoint, Truto provides a /tools endpoint that converts any configured integration into LLM-ready JSON schemas. This abstracts away the authentication boilerplate and pagination quirks, while giving your agent a strict, deterministic schema to follow.
Every resource on the EasyPost API is mapped to standard tools. You fetch these dynamically based on the active integrated account.
import { TrutoToolManager } from 'truto-langchainjs-toolset';
// Initialize the tool manager for a specific integrated EasyPost account
const toolManager = new TrutoToolManager({
trutoApiKey: process.env.TRUTO_API_KEY,
integratedAccountId: 'easy_post_account_12345',
});
// Fetch the tools to bind to your LLM
const easyPostTools = await toolManager.getTools();This single call returns the complete suite of EasyPost capabilities, fully annotated with descriptions and parameter definitions that models like GPT-4o or Claude 3.5 Sonnet understand natively.
Hero Tools for EasyPost
To build a highly capable logistics agent, you need to expose the right high-leverage primitives. Exposing generic read methods is not enough. Here are the hero tools that enable autonomous shipping workflows.
create_a_easy_post_address
Address validation is the foundation of reliable shipping. This tool creates an address in EasyPost. More importantly, it can trigger EasyPost's address verification system to catch missing apartment numbers, fix postal codes, and flag residential vs commercial delivery zones. Because addresses are immutable, the agent must use this tool perfectly on the first pass or generate a new record.
"I have a customer at 123 Main St, San Francisco, CA. Create an address record for them, ensure residential status is flagged correctly, and verify the deliverability before proceeding."
create_a_easy_post_customs_item
For international fulfillment, this tool is mandatory. It allows the agent to declare physical goods, specifying the description, quantity, weight, value, and Harmonized System (HS) tariff number. The agent will call this tool iteratively for every unique SKU inside a package before assembling the final customs declaration.
"We are shipping three cotton t-shirts and one leather wallet to Canada. Create the necessary customs items for these products, including accurate weights and standard textile tariff codes."
create_a_easy_post_shipment
This is the core rating engine. The agent uses this tool to combine an origin address, destination address, and physical parcel dimensions. When valid values are provided, EasyPost automatically populates the response with live shipping rates from all configured carriers (USPS, FedEx, UPS, DHL, etc.). This tool returns the shipment ID required to actually purchase postage.
"Take the verified destination address and our standard 10x8x6 inch parcel weighing 32 ounces. Create a shipment record so we can retrieve the live carrier rates."
easy_post_shipments_list_smartrates
Standard rates tell you how much a shipment costs. SmartRates tell you when it will actually arrive. This tool returns predictive time-in-transit data for a specific shipment ID, giving the agent percentiles (e.g., 90% chance of delivery in 3 days). This is vital for agents tasked with SLA-driven routing.
"For the shipment we just created, pull the SmartRates. Find the cheapest carrier service that has a 90th percentile delivery estimate of fewer than 4 transit days."
easy_post_shipments_buy
This tool finalizes the transaction. The agent passes a shipment ID and a selected rate ID, instructing EasyPost to charge the account, generate the live tracking code, and produce the printable postage label url.
"Purchase the shipment using the USPS Priority Mail rate ID we identified. Return the tracking number and the PNG label URL so I can log it in our database."
easy_post_shipments_refund
When orders are canceled or labels are generated in error, this tool requests a refund from the carrier. Agents must be instructed on carrier-specific rules - USPS labels can generally be refunded within 30 days of generation, while UPS and FedEx allow up to 90 days.
"The customer just canceled order #9942. Find the associated shipment ID and process a refund request for the unused shipping label."
To view the complete inventory of available EasyPost tools, schemas, and parameters, visit the EasyPost integration page.
Workflows in Action
Individual tools are useful, but chaining them together creates true autonomous logistics operations. Here is how an AI agent executes complex domain-specific tasks.
Scenario 1: Autonomous Cross-Border Fulfillment
Shipping internationally requires stringent compliance. When a customer in Germany buys a product from a US warehouse, the agent must orchestrate a multi-step sequence to ensure the package clears customs without delay.
"We received an order for a mechanical keyboard going to Berlin, Germany. The package weighs 64 ounces. Verify the delivery address, generate the customs declaration, find the most reliable shipping rate, and purchase the label."
create_a_easy_post_address: The agent creates and verifies the destination address in Berlin, correcting any formatting errors.create_a_easy_post_customs_item: The agent generates a customs item for the "Mechanical Keyboard" including the declared value, weight, and origin country.create_a_easy_post_customs_info: The agent wraps the item ID into a customs information payload, setting the contents type to "merchandise".create_a_easy_post_shipment: The agent passes the origin address, verified destination address, parcel details, and customs info ID to fetch international rates.easy_post_shipments_buy: The agent selects the optimal international carrier rate and purchases the shipment, returning the tracking code to the user.
Scenario 2: SLA-Driven Rate Shopping
A logistics manager wants to automate order routing based on strict delivery promises, ignoring brand loyalty to specific carriers in favor of mathematical probability and cost.
"I need to ship this 5 lb box to Chicago. Our SLA guarantees delivery within 3 days. Find the absolute cheapest carrier that meets this SLA with a 95% probability, and buy that label."
create_a_easy_post_shipment: The agent creates the shipment with the standard origin, destination, and parcel payload.easy_post_shipments_list_smartrates: The agent queries the predictive transit data for that specific shipment.- Agent Logic Evaluation: The LLM evaluates the returned array, filtering out any rates where the 95th percentile delivery time exceeds 3 days, and sorts the remaining options by price.
easy_post_shipments_buy: The agent executes the purchase against the winning rate ID.
graph TD
A["Agent Core"] -->|"1. Create Shipment"| B["EasyPost API"]
B -->|"Shipment ID"| A
A -->|"2. Fetch SmartRates"| B
B -->|"Transit Percentiles"| A
A -->|"3. Analyze 95th Percentile & Cost"| A
A -->|"4. Buy Rate"| B
B -->|"Tracking Code & Label"| ABuilding Multi-Step Workflows
To run these workflows in production, you must implement a robust agent loop. AI agents are prone to failure if they cannot recover from external API disruptions. Because Truto normalizes EasyPost's endpoints but does not mask underlying rate limits, your framework code must handle execution faults gracefully.
The following architecture demonstrates how to bind Truto's tools to a LangChain agent while implementing a resilient execution loop that respects HTTP 429 rate limits.
1. Initialize and Bind Tools
First, initialize the Truto Tool Manager and bind the specific write and read tools required for logistics orchestration.
import { ChatOpenAI } from "@langchain/openai";
import { TrutoToolManager } from "truto-langchainjs-toolset";
async function createLogisticsAgent(accountId: string) {
const llm = new ChatOpenAI({ modelName: "gpt-4o", temperature: 0 });
const toolManager = new TrutoToolManager({
trutoApiKey: process.env.TRUTO_API_KEY,
integratedAccountId: accountId
});
// Fetch tools from Truto
const tools = await toolManager.getTools();
// Filter to just our hero tools for safety
const allowedTools = tools.filter(tool =>
['create_a_easy_post_shipment',
'easy_post_shipments_buy',
'easy_post_shipments_list_smartrates',
'create_a_easy_post_address'].includes(tool.name)
);
// Bind the tools to the LLM natively
return llm.bindTools(allowedTools);
}2. The Resilient Agent Loop
When the agent decides to invoke a tool, the request routes through Truto to EasyPost. If you are processing a massive batch of orders, EasyPost will eventually return a 429 Too Many Requests. Truto passes this directly back to your application with standard headers.
Your execution logic must catch this exception, read the ratelimit-reset header, sleep for the required duration, and then inject a system message back into the LLM's context instructing it to retry the exact same function call.
import { HumanMessage, AIMessage, SystemMessage } from "@langchain/core/messages";
async function executeResilientWorkflow(agent, prompt: string) {
const messages = [new HumanMessage(prompt)];
while (true) {
const response = await agent.invoke(messages);
messages.push(response);
if (!response.tool_calls || response.tool_calls.length === 0) {
// The agent has finished its work and provided a final answer
return response.content;
}
// Execute the requested tools
for (const toolCall of response.tool_calls) {
try {
// Find the corresponding tool in our manager
const tool = allowedTools.find(t => t.name === toolCall.name);
const result = await tool.invoke(toolCall.args);
messages.push({
role: "tool",
tool_call_id: toolCall.id,
content: JSON.stringify(result)
});
} catch (error) {
if (error.status === 429) {
// Extract the normalized IETF reset header provided by Truto
const resetTime = error.headers['ratelimit-reset'] || 5;
console.log(`Rate limited by EasyPost. Sleeping for ${resetTime} seconds...`);
await new Promise(resolve => setTimeout(resolve, resetTime * 1000));
// Instruct the agent to try again
messages.push({
role: "tool",
tool_call_id: toolCall.id,
content: "Error: 429 Rate Limit Exceeded. The system has backed off. Please retry the exact same tool call."
});
} else {
// Handle standard 400 validation errors (e.g. invalid customs info)
messages.push({
role: "tool",
tool_call_id: toolCall.id,
content: `Error executing tool: ${error.message}. Please correct the parameters and retry.`
});
}
}
}
}
}This pattern separates your core business logic from the chaos of third-party API state management. The LLM handles the reasoning - deciding when to verify an address or compare rates - while your deterministic code handles the strict network realities of the EasyPost infrastructure.
sequenceDiagram
participant Agent as AI Agent
participant Truto as Truto Tool Manager
participant Upstream as EasyPost API
Agent->>Truto: Call create_a_easy_post_shipment(args)
Truto->>Upstream: POST /v2/shipments
Upstream-->>Truto: 429 Too Many Requests
Truto-->>Agent: HTTP 429 with ratelimit-reset
Note over Agent: System sleeps for reset duration
Agent->>Truto: Retry create_a_easy_post_shipment(args)
Truto->>Upstream: POST /v2/shipments
Upstream-->>Truto: 201 Created (Shipment ID)
Truto-->>Agent: Success ResponseAccelerating Logistics Automation
Connecting EasyPost to your AI agents transforms your shipping infrastructure from a static rules engine into a dynamic, context-aware logistics operator. Instead of writing endless IF/ELSE statements to manage international customs rules, multi-carrier fallback logic, and address formatting edge cases, you equip an LLM with the exact primitives it needs to solve problems autonomously.
By leveraging Truto's /tools endpoint, you bypass the massive engineering burden of reading docs, mapping schemas, handling authentication lifecycles, and standardizing pagination. Your agent gets deterministic, perfectly typed tools out of the box. Your engineers get to focus on refining the model's prompts and orchestration layers, completely removing the SaaS integration bottleneck from your development lifecycle.
FAQ
- Does Truto automatically retry failed EasyPost tool calls?
- No. Truto passes upstream rate limit errors (HTTP 429) directly to your application. It standardizes the rate limit headers (ratelimit-reset, ratelimit-remaining), but the agent framework is responsible for implementing retry and backoff logic.
- Can an AI agent update an existing address or parcel in EasyPost?
- No. EasyPost Address and Parcel objects are strictly immutable after creation. If an agent makes a mistake, it must create a completely new object rather than attempting to update the existing one.
- Which AI agent frameworks work with Truto's EasyPost tools?
- Truto's tools are framework-agnostic. By exposing standard JSON schemas, they can be natively bound to LangChain, LangGraph, CrewAI, the Vercel AI SDK, or any custom LLM execution loop.
- How do AI agents handle international shipping via EasyPost?
- Agents must sequence a specific set of tools: they create CustomsItems for every product, bundle them into a CustomsInfo object, and pass that object ID into the final Shipment creation tool.