
FastAPI for AI Agents: Routes, Validation and Queues
How to structure FastAPI for AI agents that connect to WhatsApp, CRM and ERP: routes, validation, queues, idempotency and error handling that holds up.
Want to apply this in your company?
Book a call with Juan →FastAPI for AI Agents: Routes, Validation and Queues
To connect an AI agent to WhatsApp, a CRM and an ERP with FastAPI, split the API into three parts: inbound routes that only receive and validate events, a queue that runs the agent outside the request, and tool routes the agent uses to read and write in your systems with minimum permissions. The agent never talks directly to the ERP or the CRM. It goes through an API you own, which validates, limits and logs every action.
Key takeaways
- The webhook route should respond fast and only enqueue. The agent's work happens afterward, in a worker.
- Each agent tool is a narrow route, named after a business action, with a validated schema and its own permission.
- Idempotency is not optional: webhooks are redelivered and queues retry tasks, so every write must be safe against duplicates.
- Errors need three clear destinations: retry, stop and log, or hand off to a person.
- Start with one real workflow, with logs and an agreed metric, before adding more integrations.
Why put an API between the agent and your company systems?
It is common to start with the agent calling the ERP or CRM API directly, using a broad credential. That works in a demo and fails in production, because there is no single point to validate, limit, log and shut off.
A FastAPI layer fixes this by being the place where the rules live: which actions exist, which fields are accepted, who can call what, and what happens when something fails. FastAPI helps because it uses Pydantic to validate input and output data, generates API documentation from your schemas, and has dependency injection, which is useful for authentication and per-route access control. The official FastAPI documentation covers all of these features.
If your team is still choosing a language, read Python or TypeScript for building AI agents. Here we assume Python, which is where FastAPI lives.
Which routes does an AI agent API need?
A split that tends to work organizes routes by responsibility, not by system. This is a suggested structure (a proposed implementation, not a standard):
- Inbound routes: GET and POST on /webhooks/whatsapp, POST on /webhooks/crm and POST on /webhooks/erp. They only verify the signature, validate the format, store the event and enqueue it.
- Tool routes: for example POST /tools/order/lookup, POST /tools/opportunity/create and POST /tools/support/handoff. These are the agent's only path into your systems.
- Operations routes: GET /health, GET /events/(id) to check the state of a processing run, and POST /events/(id)/reprocess, protected and limited to authorized people.
- Admin routes: pause the agent, list stuck items in the queue and review the decision log.
With the WhatsApp Cloud API, Meta uses a GET verification call when you register the webhook and sends messages by POST, with a signature header you must check. Setup details are in how to integrate AI into your company's WhatsApp and, for teams that prefer to orchestrate with a visual flow, in the n8n WhatsApp Cloud API workflow guide.
The important detail is how you name tools. Prefer lookup_order over a generic route that forwards any call to the ERP. A generic route turns the agent into a user with full power, and the model ends up deciding what is allowed.
How do you validate input and output without trusting the model?
There are two validation points, and both matter.
On the way in, the webhook payload is validated with a Pydantic model. Anything that does not match the schema is rejected and logged, and never reaches the agent. This also lowers the risk of malformed or forged events triggering work.
On the agent's way out, before a tool runs, the parameters pass through another Pydantic model, the one attached to the route. If the language model proposes an order ID in the wrong format, a negative quantity or a field that does not exist, FastAPI returns a validation error and the tool does not run. That error goes back to the agent as structured information, so it can correct itself or ask the customer for the missing data.
Practical rules:
- Use restricted types: enumerations for statuses and action types, length limits for text, formats for identifiers.
- Forbid extra fields in your models, so invented parameters fail instead of being silently ignored.
- Treat customer text as untrusted input. A message can try to push the agent into calling a tool with improper parameters. The article on prompt injection protection for AI agents covers this risk in detail.
- Check authorization on the server, never in the prompt. The rule that the agent may only look up orders belonging to the phone number in the conversation must live in the route code.
For access control, OWASP's authorization guidance reinforces principles that fit here: least privilege, deny by default and check permission on every request.
Why should the agent run in a queue instead of inside the webhook?
The reason is practical. An agent can take a while to respond, because it calls the model, queries tools and sometimes repeats steps. If that happens inside the webhook request, the provider may treat the delivery as failed and send the event again. The result is a duplicate reply to the customer.
The safer pattern is the one below, and Stripe's article on webhooks describes the same idea of acknowledging delivery quickly and processing later:
- The route receives the event and checks the signature.
- It stores the event with the provider's original identifier.
- If the identifier already exists, it responds with success and does not enqueue again.
- It enqueues the task and responds immediately.
- A worker picks up the task, runs the agent and records the result.
FastAPI's BackgroundTasks runs in the same process as the API. It suits lightweight tasks with no durability requirement, such as writing an extra log line. For the agent, use an external queue with separate workers, for example Celery, ARQ or RQ with Redis, or whichever broker your team already operates. The reason is durability: if the process restarts, the task cannot disappear.
Other queue precautions:
- Order per conversation: messages from the same customer must be processed in sequence, or the agent answers an old question.
- Limits on steps, time and spend per task, to prevent an AI agent infinite loop.
- A dead-letter queue for tasks that ran out of retries, visible to someone on the operations team.
How do you make CRM and ERP writes idempotent?
Idempotency means that repeating the same operation does not create a second effect. In agent integrations this is essential, because there are three sources of repetition: the provider resending the webhook, the queue retrying the task, and the agent itself calling the tool twice.
Stripe's documentation on idempotent requests is a good example of how a provider defines this contract with a key sent by the client. You can apply the same idea internally:
- Generate one idempotency key per action, derived from the source event and the tool name, for example the message ID combined with the action.
- Store the key together with the result. If the same key arrives again, return the saved result instead of executing.
- When the ERP or CRM does not accept an idempotency key, check before writing: look up the opportunity or order by a reference field and create it only if it does not exist.
- Separate read tools, which can repeat freely, from write tools, which require the key.
What should happen when WhatsApp, the CRM or the ERP fails?
Without a policy, every failure becomes improvisation. Define three destinations for any error, using this decision guide:
- Temporary failure (timeout, rate limit, service unavailable): retry with increasing wait times and a cap on attempts. Respect the retry-after header when the provider sends one.
- Permanent failure (invalid data, record not found, permission denied): do not retry. Log the cause, return a structured error to the agent and, if no fix is possible, route it to a person.
- Judgment failure (the agent lacks enough information, the request is out of scope or involves a decision it should not make): hand off to a human agent with a summary of the conversation.
A few details that prevent problems:
- If the ERP is down, the agent should tell the customer it will check and get back to them, instead of inventing a status. What the customer is told is a product decision, not something left to the model.
- Outbound messages outside the WhatsApp service window require approved templates and consent. See WhatsApp outbound messages: templates and opt-in before you program automatic resends.
- Use a single error format across routes, with a code, a message and a flag saying whether retrying makes sense. This makes life easier for both the worker and the agent.
- Log every call with a correlation ID, tool name, result and duration. OpenTelemetry's introductory observability material helps you think about logs, metrics and traces consistently.
On LGPD (Brazil's data protection law), the design rule is to send the model only what it needs. Mask documents and any data the task does not require, and define how long logs are kept and who can access them. For questions about data processing, the ANPD (Brazil's data protection authority) is the official reference.
Illustrative scenario: order status lookup
Imagine a fictional distributor whose customer asks on WhatsApp where their order is. The flow in the API looks like this:
- Meta sends the webhook. The route checks the signature, validates the payload, stores the event with the message ID and enqueues it.
- The worker opens the conversation, loads the minimum context and runs the agent with a step limit.
- The agent calls lookup_order. The route verifies that the conversation's phone number is linked to the order's customer and returns only the status and estimated delivery fields.
- The ERP is slow to respond. The worker retries with increasing wait times. If attempts run out, the agent tells the customer it will confirm shortly, and the event goes to the stuck-items queue with an alert to operations.
- If the customer asks to change the delivery address, the write tool does not exist in this pilot. The agent calls the handoff tool with a summary, and a person takes over.
In this design, the agent never sees the ERP credential, never accesses another customer's order and never performs an action that is not on the route list.
How to apply map, limit, pilot and hand over
Juan Carlo's working method follows four moves, and they translate well to the API.
Map: document the current workflow, who does what, in which systems, and what the starting point is for time and errors. Without that baseline, there is no way to know whether the pilot helped.
Limit: define which data the agent sees and which actions it can run. Each action becomes a route with a schema, a permission and a log. Anything not on the list does not exist for the agent.
Pilot: pick one real workflow, such as order status or opportunity creation, with logs and an agreed metric before you start. The stuck-items queue and the decision log are part of the pilot, not extras.
Hand over: at the end, deliver documentation, routes, an operations dashboard and named owners, and decide with the team whether to expand, adjust or stop. Stopping is a legitimate outcome of a well-run pilot.
Checklist before going to production
- Webhooks with signature verification and fast responses.
- Events stored with the provider's identifier and protection against duplicates.
- A durable external queue, with a separate worker and ordering per conversation.
- Tool routes named after business actions, with restricted Pydantic models.
- Authorization checked on the server, with separate credentials per system and scope.
- An idempotency key on every write.
- An error policy with three destinations: retry, stop or hand off.
- Limits on steps, time and spend per task.
- Logs with a correlation ID, a defined retention period and sensitive data masked.
- A pause button for the agent and a tested handoff route to a human.
Frequently asked questions
Do I need a queue if my message volume is small? We recommend one even at low volume. The reason is not scale but durability and protection against redelivery: the provider may repeat the webhook and the agent may be slow. A simple queue already separates receiving from processing and prevents duplicate replies.
Can I use FastAPI's BackgroundTasks to run the agent? For prototypes and lightweight tasks, yes. In production, it runs in the same process as the API, so a restart can lose the task and there is no control over retries. For agents that call models and external systems, a queue with separate workers is safer.
Should the agent have direct write access to the ERP? Our recommendation is no, at least at the start. Expose specific actions through routes in your own API, with validation, an idempotency key and minimum permissions, and begin with read access. Writes come in gradually, with human approval when the impact is high.
How do I know whether the integration is working well? Before the pilot, define a simple metric tied to the process, such as conversations resolved without an improper handoff or time to the first useful reply, and compare it with the baseline you mapped. Also watch the stuck-items queue and errors per tool, because they show where the design needs adjusting.