Blog • Artigo
    ArquiteturaAPIAnthropic

    Leading the technical integration of the Anthropic API: what I learned architecting systems with Claude in production

    Recently, when integrating Anthropic's API into SIA, a multi-tenant conversational platform for customer service, I consolidated decisions that had already emerged in other AI projects, legal platforms, conversational CRM, recruitment with specialized agents, and automation systems. In all of them, the real challenge was never calling the API. The challenge was deciding where the model enters the architecture, where it should stop, and where deterministic code needs to take control.

    Alexsander
    AlexsanderEngenheiro de Software
    Jul 04, 2026
    11 min de leitura
    Leading the technical integration of the Anthropic API: what I learned architecting systems with Claude in production

    This is the article I wish I had read before leading my first serious integration with Claude. It's not a beginner's tutorial. It's a dense technical account of how I think about integration when I'm the one responsible for the architecture, production cost, and maintainability of the system two years from now, with practical notes I use to justify each decision to the team and to the client.

    The mistake I see repeated: treating the API as a chat endpoint

    Most teams that integrate the Anthropic API for the first time make the same structural mistake. They treat the model as if it were a substitute for a chat call, sending a message, receiving a response, and printing it on the screen. It works in the prototype and breaks in the first week of real production, because production requires things that a chat doesn't require: context that persists between calls, tools that the model can invoke, cost control per request, predictability of output format, and a containment plan for when the model fails.

    My first decision as a technical leader in any integration is to clearly separate two layers. The decision layer, where the model reasons, chooses tools, and produces language, and the execution layer, where the deterministic system validates, persists, and applies business rules that cannot depend on statistical inference. This separation seems obvious when written, but in practice, it's the line that most teams blur under deadline pressure, and it's exactly this blurred line that later becomes technical debt that's impossible to pay.

    The Messages API, which is the core of the Anthropic API, was designed precisely to support this separation. Each call receives a list of messages, a model, an output token limit, and optionally, a set of tools and a system prompt. What the team decides to do around this contract determines whether the architecture will scale or become a tangle of giant prompts trying to do the work that should be in the code.

    One point that many teams forget from the start: the Messages API is stateless. There is no persisted session on the Anthropic side between one call and another; who builds, summarizes, and re-sends the history with each request is my application. This means that conversational state, summarized history, executed tool events, and data retention policy are entirely my responsibility, not the model's. Claude receives the context I decide to send; it's not the owner of that context, and treating this boundary with clarity avoids a common mistake, which is to deposit in the model an expectation of memory that it simply doesn't have by contract.

    It was precisely this principle that I applied in SIA. The domain core, written in hexagonal architecture, never knows the details of how the model is called. Claude occupies a specific port of the hexagon, the reasoning and language generation port, and never has direct access to the database or decides alone whether a reservation can be canceled. Every action with real side effects goes through a deterministic validation layer before becoming an operation in the system. The model suggests; the code decides.

    Choosing the right model is not about choosing the strongest

    A decision that recurrently falls on whoever leads the integration is which model to use at each point in the system. Instead of tying the architecture to the commercial name of the model, which quickly becomes outdated because Anthropic launches and retires versions frequently, I treat each model as an operational profile. There's the low-cost, low-latency profile, currently occupied by Haiku, the balance profile for most of the production load, currently occupied by Sonnet, the deep reasoning profile for expensive decisions, currently occupied by Opus, and the frontier capability profile with stricter access restrictions, currently occupied by Mythos-level models. This abstraction layer, which I maintain in the system configuration and not spread throughout the code, is what allows me to switch model versions without rewriting the entire routing logic.

    In practice, I use Haiku for classification, structured extraction, and any high-volume step where the tolerable error margin is greater, such as the initial intention routing in SIA, which decides whether the user's message is a simple availability query or something that requires deeper reasoning. I use Sonnet as the workhorse for most of the agent logic, response generation, and tool orchestration, because that's where the cost-benefit relationship becomes most favorable for real production loads. I reserve Opus for the points in the system where deep reasoning really changes the outcome, complex legal analysis in Kazo.ai, code architecture, decisions that require long chains of inference.

    Technical note: This routing by complexity doesn't need to be done by a separate model. It's possible to use Haiku itself as a complexity classifier at the input of the decision graph, with low enough latency not to impact the experience, and only scale up to Sonnet or Opus when the classification indicates a real need. This avoids the most common mistake, which is choosing the most capable model for everything, thinking that this eliminates risk. It only transfers the risk of quality to the risk of cost and latency, and in systems with real volume, this second risk usually hurts more quickly than the first.

    Tool use is where the architecture really happens

    If there's a single feature of the API that separates an amateur integration from a staff-level integration, it's the correct use of tool use. The central idea is simple: you declare tools with a name, description, and parameter schema; the model decides when to invoke them, and your code executes the real logic and returns the result in the next call.

    The point that most teams underestimate is that the tool description is, in practice, a piece of prompt engineering as important as the system prompt itself. Poorly described tools generate incorrect calls, incomplete parameters, and decisions on when to use the tool that don't make sense for your domain. I treat the design of each tool as I would the design of a public API endpoint, with a clear contract, examples of correct and incorrect use documented in the description itself, and rigid validation on my code side, never blindly trusting what the model returns.

    In SIA, the most critical tool was not generic; it was domain-specific: fleet availability queries, reservation creation and cancellation, verification of each lessor's policy. I declared each one with the same rigor I apply to a public endpoint:

    Código
    tools = [
        {
            "name": "cancelar_reserva",
            "description": (
                "Cancels an existing reservation, identified by the reservation ID. "
                "Use only when the user explicitly confirms the intention "
                "of cancellation. Do not use to query cancellation policy, "
                "use the consultar_politica_tenant tool for that. Returns an error "
                "if the reservation is already canceled or outside the allowed window."
            ),
            "input_schema": {
                "type": "object",
                "properties": {
                    "reserva_id": {"type": "string"},
                    "motivo": {"type": "string"}
                },
                "required": ["reserva_id"]
            }
        }
    ]
    

    This level of description, including what the tool should not do and where to redirect the intention when the case does not apply, measurably reduced the rate of incorrect calls in SIA's first tests. The tool is called by the model, but the actual cancellation only happens after my code checks if that reservation belongs to the correct tenant, if it's within the cancellation window, and if the authenticated user has permission over it.

    The API also offers a set of ready-to-use server tools, web search, file search, sandbox code execution, text editing, a controlled shell, and even a tool for searching the catalog of available tools, useful when the agent has access to too many tools to fit entirely in the prompt. There's also an agent-side memory feature, designed to persist learning between sessions without relying entirely on conversation history. I use server tools when the maintenance gain compensates for the loss of fine control, and I write my own domain-specific tools when the business logic is too specific to depend on a generic platform component, which was the case in virtually all critical tools in SIA and Kazo.ai.

    Technical note: For agents that make multiple tool calls in sequence, it's worth reviewing how the model handles parallel tool calls and how the streaming flow interleaves text blocks with tool call blocks. The consumer code needs to correctly remount this state before deciding whether and when to execute a real action, and this is one of the points where I most often see subtle bugs in first-time implementations.

    Tools with side effects require idempotence

    Validating the call before executing solves part of the problem, but not all of it. The question I ask in every architecture review is what happens if the same tool is called twice. And if there's a network retry after the operation has already been executed on my system's side. And if the user confirms cancellation and the connection drops before the response comes back, leading the client to try again. And if the same queue event is reprocessed by mistake.

    In SIA, every business action with side effects carries its own operation identifier, derived from the tenant, user, and intention, not just the technical ID of the tool call. This identifier functions as an idempotence key, which means executing the same operation twice with the same key produces the same final result, without duplicating the effect. I modeled the lifecycle of each action as an explicit state machine, requested, validated, confirmed, executed, failed, instead of treating execution as a binary success or failure event. This allows me to audit exactly where an operation got stuck when something goes wrong, reprocess safely from the last consistent state, and maintain a dead letter queue for cases that require human intervention instead of automatic retry. For irreversible actions, such as definitive cancellation outside the tolerance window, I require explicit additional confirmation before moving the state to executed, even if the model has already interpreted the user's intention clearly.

    This level of rigor seems like an exaggeration in a prototype, but it's exactly what separates an agent that works well in a demonstration from an agent that survives unstable networks, client retries, and queue reprocessing in real production.

    Structured output as a contract, not a suggestion

    Systems in production rarely consume free text. They consume JSON that feeds another service, populates a database, or triggers a workflow. The Anthropic API offers native support for structured output, which eliminates much of the fragile text parsing work that dominated the first generations of LLM integrations.

    As a technical leader, I insist that any point in the system that needs structured data uses this feature from day one, instead of asking the prompt for the model to respond in JSON and hoping the format remains stable. The difference between these two approaches is the difference between an API contract and a verbal promise. A validated schema on the server side, with data type, required fields, and permitted values defined explicitly, is what allows treating the model's output as any other system boundary, with automated contract tests, including edge cases that the schema should reject.

    But it's essential not to confuse schema compliance with domain correctness. Schema guarantees form, not truth. A perfectly valid JSON, with all required fields filled and all enums within the permitted set, can still carry an incorrect business decision, a misinterpreted date, or a monetary value outside the operation's reality. I treat the schema as the first quality barrier, not as final authorization, and maintain separate domain validation in the code for the rules that no generic schema can express. I also treat the schema itself as a versioned contract, any change in required fields or enum is a breaking change for whoever consumes this output, with the same care I would have when altering a public API contract.

    Extended thinking and fine control of reasoning effort

    The extended reasoning mode allows the model to work on a problem in stages before producing the final response, which usually significantly improves quality in multi-step tasks, mathematics, planning, and complex logic debugging. The platform also exposes finer controls over this behavior, including adjustment of reasoning effort and task budget, which allows calibrating how much the model can spend thinking before responding, instead of treating extended reasoning as on or off.

    The decision on where to activate this mode is as much a product decision as it is technical. In a triage step that

    Este conteúdo foi útil?
    Compartilhar artigo

    Quer aplicar isso no seu contexto?

    Vamos conversar sobre seus desafios e encontrar o melhor caminho para sua operação.

    Agendar conversa