DEV Community

Cover image for How AI Endpoints Change the Traditional API Flow
Daniel Balcarek
Daniel Balcarek Subscriber

Posted on • Edited on

How AI Endpoints Change the Traditional API Flow

Validating probabilistic outputs costs credits

As a backend developer, I have built hundreds of endpoints, so the typical endpoint flow is deeply ingrained in how I think about web applications. But when I started building AI-powered endpoints, I noticed an interesting shift.

At first, AI endpoints looked like simple proxy endpoints with some configuration for connecting to a model:

API receives request
↓
send prompt to model
↓
receive response
↓
return it to the client
Enter fullscreen mode Exit fullscreen mode

And it worked well until I found out that passing a prompt directly from the client was not a good idea. The endpoint could be misused for a completely different purpose, allowing someone else to consume my AI usage credits.

Then I realized that the input also needed limits. Sending a large context for a specific task costs more and may produce unexpected results.

So when I started looking closer, especially when I needed reliable structured output and predictable application behavior, I quickly realized that it was not that simple. Validation was no longer only guarding execution, it had also become a post-processing step. AI models are probabilistic. Even with the same input, they may return different outputs, omit required information, misunderstand instructions or return something that is technically valid but logically wrong. And because every token has a price, I cannot simply retry the request and hope for a better result.

That was when I started questioning whether AI endpoints should be designed in the same way as conventional Web API endpoints.

Table of Contents

Conventional Web API Endpoint Flow

A conventional Web API endpoint usually follows a similar flow:

validate request
↓
execute business logic
↓
return representation
Enter fullscreen mode Exit fullscreen mode

The first phase is request validation. We validate the incoming data against property constraints, API contracts, authorization rules and application-specific business rules.

The second phase is execution. The application processes data, performs I/O operations or executes business logic.

The final phase is returning a representation of the result. The code executed by a conventional endpoint is normally deterministic within a known application state. When the same code runs against the same state, we generally get the same result.

And that is basically it.

The general flow of a conventional Web API endpoint is relatively simple, although the individual steps can, of course, be very complex. But AI models do not work in exactly the same way. They do not simply execute a predefined sequence of instructions and always produce the same result.

AI-powered Web API Endpoint Flow

The internal flow of an AI-powered endpoint is usually more complicated:

validate request
↓
prepare prompt, tools and context
↓
generate probabilistic output
↓
validate schema, meaning and safety
↓
retry, repair, reject or fall back
↓
return representation
Enter fullscreen mode Exit fullscreen mode

We still start by validating the incoming request. Standard property constraints and business rules are still important, but AI endpoints may also require additional controls. The application may need to restrict the requested topic, limit input size, apply controls against suspicious instructions, isolate untrusted retrieved content or decide which tools the model is allowed to call.

The next step is preparing the prompt and settings for the model call. The application prepares the instructions, conversation history, retrieved context, tool definitions and generation settings.

Then the model produces an output. But unlike the result of conventional business logic, we cannot automatically assume that the output is correct just because the model call succeeded. A successful HTTP response from the model provider only tells us that the model generated something. It does not tell us whether the result is complete, safe, grounded or even useful.

So validation appears again after generation. For example, we may validate the JSON schema in a similar way to a response from a conventional external service. But even when the schema is correct, the result may still be logically wrong for the business domain. The model may also omit properties that are not technically required by the schema but should be present based on the provided context.

When the output fails these checks, the application must decide what to do next. It may try to repair the response, ask the model to generate it again, use a fallback model, return a controlled error or send the request for human review. But every decision has its own cost. A retry consumes more tokens, while human intervention costs both time and money.

All of this turns the endpoint into an orchestration pipeline instead of a simple proxy around a model call.

What This Difference Changes

With a conventional Web API, the application explicitly defines how the result is created. This means that the execution path is controlled by code and we know what result to expect.

With an AI-powered Web API, the application delegates part of the result creation to a probabilistic system. This means that we no longer fully control how the result is generated. To achieve the expected result, we need to add extra steps to the flow, such as post-processing and output validation.

We can simplify it like this:

  • Conventional Web API: the backend executes the rules.
  • AI-powered Web API: the backend orchestrates and evaluates the result.

But this difference affects much more than validation. It also changes how we think about latency, retries, idempotency, testing, observability, cost and output contracts.

There are definitely more differences, but let’s have a look at the ones I discovered along the way.

Unpredictable Latency

The latency of a conventional endpoint is usually determined by business logic, I/O operations, data processing and similar operations. These operations are not always fast or perfectly predictable, but we can usually measure them separately and optimize the slowest parts by improving the code, optimizing database queries or adding caching.

AI endpoints introduce another level of variability.

Generation time may depend on the selected model, prompt and context size, output length, provider load and other factors. One request may finish after a single model call, while another may require several tool calls, validation attempts or regeneration steps.

So latency is no longer determined only by the operations we explicitly execute. It may also depend on decisions made during model generation.

That makes timeouts, cancellation, streaming, asynchronous processing and latency budgets even more important.

One of the easiest ways to improve the latency of an AI-powered endpoint is to improve the prompt. We can make it more specific, reduce unnecessary context and limit the expected output.

Another example is avoiding the need for the model to return full objects.

For example, we may provide data like this:

{
  "data": [
    {
      "id": 1,
      "name": "john doe",
      "address": "Mordor",
      "category": "Nazgûl"
    },
    {
      "id": 2,
      "name": "joe doe",
      "address": "Gondor",
      "category": "soldier"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Then we can instruct the model to return only the selected IDs instead of repeating the full objects.

The backend can map those IDs back to the original data after generation. This reduces the number of output tokens and may improve both latency and cost.

The main point is that we approach latency optimization differently. With conventional endpoints, we usually optimize code, database access or caching. With AI endpoints, we also need to optimize prompts, context size, model selection and generated output.

Retry Logic

In conventional Web APIs, we often retry I/O operations under specific conditions. For example, we may retry when a dependency is temporarily unavailable, a connection is interrupted or an external service returns a transient status code. These retries are usually based on technical failures.

AI endpoints introduce another type of retry. The request may complete successfully at the transport level, but the generated output may still be unusable. For example, it may miss a required field, break the expected schema, contradict the supplied context or fail a business rule.

Every retry increases latency and consumes additional tokens, which means additional cost. The next attempt may return a different but still incorrect response.

Because of that, AI retries should not be treated like ordinary network retries. They need explicit limits, and we should also consider additional constraints such as the token and monetary budget, the reason for the failure, whether another attempt is likely to help, and whether a fallback model or deterministic alternative is available.

Let’s have a look at this C# retry pipeline, which I used with a local model exposed through Ollama:

private static class RetryPipeline<T>
{
    public static readonly ResiliencePipeline<Result<T>> Instance = new ResiliencePipelineBuilder<Result<T>>()
        .AddRetry(new RetryStrategyOptions<Result<T>>
        {
            MaxRetryAttempts = 2,
            Delay = TimeSpan.FromSeconds(2),
            BackoffType = DelayBackoffType.Exponential,
            UseJitter = true,
            ShouldHandle = new PredicateBuilder<Result<T>>()
                .Handle<HttpRequestException>()
                .Handle<JsonException>()
                .Handle<TaskCanceledException>(ex =>
                   !ex.CancellationToken.IsCancellationRequested)
                .HandleResult(r => r.IsFailed)
        })
        .Build();
}
Enter fullscreen mode Exit fullscreen mode

The pipeline retries when it receives an HttpRequestException, JsonException or TaskCanceledException. It allows a maximum of two retry attempts and uses exponential backoff with an initial delay of two seconds.

So far, this looks like a typical resilience pipeline for a conventional API endpoint.

The important difference is this condition:

.HandleResult(r => r.IsFailed)

It also retries when the operation does not throw an exception but still returns a failed result.

Inside the action executed by this pipeline, I validate the AI output. If the response is technically valid but logically wrong, I return a failed result and let the pipeline try again.

Note: Simply retrying may not be enough. To prevent the model from producing another invalid result, the validation failure should be included in the next generation attempt so that the model knows what was wrong with the previous response. Without this feedback, the model may repeat the same mistake.

This approach may not be ideal when using a paid model provider because every retry consumes additional tokens and increases the cost. But for a local or free model, a small and strictly limited number of retries may be sufficient.

Sometimes the correct decision is not to retry. It may be better to reject the output, return a controlled error or ask the user for more information.

Idempotency and Side Effects

Idempotency means that executing the same operation multiple times has the same effect as executing it once.

A simple example is a conventional GET endpoint:

GET /api/orders/123
Enter fullscreen mode Exit fullscreen mode

We can call this endpoint several times without changing the order. The returned representation may change if the underlying data changes, but the request itself does not produce additional side effects.

With AI endpoints, idempotency becomes more complicated when the model can call tools.

Sending the same prompt multiple times may produce different outputs or slightly different wording. For read-only endpoints, this may be acceptable because no server state is changed.

But it becomes much more dangerous when the endpoint can perform actions. For example, the model may create an order, send an email or update data through a tool. If output validation later fails and the whole operation is retried, the model may call the same tool again and create a second order.

That is why actions triggered by AI endpoints should use protections such as idempotency keys, persisted operation results and unique constraints. Repeating the same action with the same operation ID should return the original result instead of executing the side effect again.

Testing AI Endpoints

Testing conventional endpoints is relatively straightforward when the application state and dependencies are controlled.

We prepare the data, execute the endpoint and assert the expected result, such as the status code, expected response values, an updated application state or whether an external dependency was called.

With AI endpoints, exact-output assertions are often fragile. The same valid answer may be written in many different ways. A model update may also change the wording while keeping the same meaning. But asserting only the response status or checking that fields are not null is too weak.

Instead of always comparing the exact response, we can verify the required properties of the result. Depending on the use case, we may assert that values are within an allowed range, forbidden content is not present, tool calls respect the allowlist and the response follows the expected business rules.

For example, asserting the complete response with Assert.Equal would be fragile:

var result = await endpoint.GeneratePlanAsync(request);

// Fragile assertion
Assert.Equal("Family Day in Brno", result.Title);
Assert.Equal(
    "Visit the science centre and have lunch nearby.",
    result.Description);
Assert.Equal(1_500, result.TotalCost);
Enter fullscreen mode Exit fullscreen mode

The model may return a different title or description while still producing a completely valid plan.

Instead of checking the exact response, we can assert the properties that matter to the application using methods such as InRange, Contains and DoesNotContain:

var result = await endpoint.GeneratePlanAsync(request);

Assert.NotNull(result);
Assert.NotEmpty(result.Activities);

//Assert
Assert.InRange(result.TotalCost,0,request.Budget);

Assert.All(
    result.Activities,
    activity =>
    {
        Assert.Contains(
            activity.Type,
            request.AllowedActivityTypes);

        Assert.InRange(
            activity.TravelTimeMinutes,
            0,
            request.MaximumTravelMinutes);

        Assert.False(
            string.IsNullOrWhiteSpace(activity.Name));
    });

Assert.DoesNotContain(result.Activities, activity => activity.IsClosed);
Assert.All(result.Activities,activity => Assert.NotEmpty(activity.SourceIds));
Enter fullscreen mode Exit fullscreen mode

The title, description or order of activities may change, but the generated plan must still respect the budget, travel-time limits, allowed activity types and supplied source data.

A large part of the endpoint can still be tested deterministically. Schema validation, authorization, tool permissions, parsing, fallback logic and side-effect handling should still be tested like normal application code, but we should change how we assert the generated result.

The Output Contract

When a conventional endpoint calls an external service, we usually rely on a JSON parser and a clearly defined contract. When the service returns invalid JSON or a response that does not match the expected schema, deserialization fails and the application handles the error.

AI output creates a more subtle problem.

A model may return perfectly valid JSON that matches the expected schema but is still logically wrong.

For example:

{
  "approved": true,
  "reason": "The customer meets all required conditions.",
  "failedConditions": [
    {
      "name": "age",
      "value": 13,
      "description": "The customer is under the required age."
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

This response is valid JSON and may also match the expected schema. But it contradicts itself. The customer is marked as approved even though one of the required conditions has failed.

So schema validation is necessary, but it is not enough.

AI output may require additional levels of validation, such as business-rule validation, logical consistency checks and safety validation.

The endpoint must be able to tell the difference between a response that can be parsed and a response that can actually be trusted.

Observability and Cost

Traditional endpoint monitoring usually focuses on metrics such as request count, latency, errors, dependency calls and resource usage.

AI endpoints need these metrics too, but they also introduce model-specific ones:

  • input and output token usage
  • model and model version
  • number of generation attempts
  • tool calls
  • validation failures
  • repair success rate
  • estimated cost
  • output quality or evaluation score

Without these metrics, it is difficult to understand why an endpoint became slower, more expensive or less reliable.

A provider may change the model behind an alias. Prompts may grow over time, retrieved context may become larger and retry frequency may increase. The endpoint may still return 200 OK while its cost increases and the quality of its output slowly gets worse.

Summary

Traditional endpoints execute business logic that is defined and controlled by code.

AI endpoints are different. They orchestrate probabilistic generation, validate the result and decide whether it is reliable enough to return.

That changes how we should think about and design endpoints that use AI models.

An AI endpoint should not be just a thin proxy around a model call. The model generates the output, but the backend still decides whether it should become the final response.

These are some of the biggest differences I discovered while creating, debugging and maintaining AI endpoints. There are definitely more, but even these few examples show that adding an AI model behind an endpoint can significantly change its flow.

Top comments (30)

Collapse
 
julianneagu profile image
Julian Neagu

This is exactly why I stopped thinking of AI calls as simple API wrappers. The model is only one part. The flow around it decides if the result is actually usable.

Collapse
 
sylwia-lask profile image
Sylwia Laskowska

I really like this breakdown of the challenges we still have with even a simple AI endpoint.

I think I'm going to send this article to my stakeholders. 😄 I'm working on a system that has to be strictly deterministic because it deals with legal instruments. And yet, every now and then, someone (usually from the non-technical side) suggests, "Maybe we could add some AI somewhere?"—without a clear idea of where or why, just because it sounds cool.

The problem is that in our domain, "creative" output isn't a feature. It would be quite unfortunate if AI accidentally turned the victim into the criminal. 😅 This article does a great job of explaining why these things deserve a lot more thought than simply adding an LLM to the architecture.

Collapse
 
gramli profile image
Daniel Balcarek

Thanks, Sylwia! ♥️

Yep, AI has to be everywhere now. Let’s put it into everything! It may cost more and make the system more fragile, but at least we can proudly say that we have AI in our code. 😂

It’s like hearing the same discussions at my workplace.

Collapse
 
sylwia-lask profile image
Sylwia Laskowska

Hahaha, exactly! 😂 In one of my previous projects, we didn't have a single AI feature, not because we couldn't add one, but because it simply wasn't needed and would have made the system worse. And yet, somehow we were still proudly marketed as "AI-powered." 😄

Thread Thread
 
gramli profile image
Daniel Balcarek

AI-powered? Take my money right now! 😂

Collapse
 
webdeveloperhyper profile image
Web Developer Hyper

Very informative post! 💯 I'm using AI and developing APIs at work, but I haven't worked with AI APIs yet. They seem more challenging because of the randomness of AI responses. However, thanks to your post, I think I can do it.

Collapse
 
gramli profile image
Daniel Balcarek

Thanks, I’m glad you found it informative! ❤️

Yes, AI APIs are definitely more challenging because of their probabilistic responses. And if we need trustworthy output, we have to change our mindset a little when designing these endpoints.

Collapse
 
ofri-peretz profile image
Ofri Peretz

The prompt-from-client vulnerability you hit early is structurally the same trust-boundary violation as SQL injection — user-controlled data flowing into an interpreter without sanitization. For SQL, parameterized queries give you a clean fix; for LLMs there's no equivalent abstraction yet, because system prompt, user content, and tool outputs all collapse into one token stream the model can't reliably compartmentalize. That's what makes the post-processing validation step load-bearing in a way most teams underestimate — it's not just schema enforcement, it's the only layer that can catch prompt-driven semantic drift after the model has already run and the cost is already spent.

Collapse
 
gramli profile image
Daniel Balcarek

Yes, that’s a good analogy. With LLMs, however, post-processing validation alone is not always enough. We should validate the intent before generation as well, both to keep the request within the intended use case and to reduce security risks.

So we validate the input because of intent and security concerns, and we validate the output because the model can still follow the wrong instruction or drift semantically even when the request itself looks valid.

Compared with SQL, the protection is less clean and requires multiple defensive layers.

Collapse
 
anhmtk profile image
anhmtk

This is a critical shift that I think many developers are still underestimating. I've been building AgentShare — an MCP server for Solana DeFi data — and the same principle applies: the API is no longer a data retrieval endpoint, it's a reasoning endpoint. You're not asking for a number; you're asking for a judgement (SAFE/CAUTION/AVOID).

The billing model is also evolving — x402 and token-based pricing reflect this change. It's not just about 'how many requests', but 'how much reasoning is needed'. That changes everything from caching to rate limiting to security.

Collapse
 
gramli profile image
Daniel Balcarek

Exactly! I really like the way you summarized it: number vs. judgment and how many requests vs. how much reasoning. I think those two comparisons capture the shift really well. Thanks for sharing your perspective!

Collapse
 
xulingfeng profile image
xulingfeng

This maps pretty well to how I think about AI test automation. You don't assert exact output — you assert behavioral properties: was the right tool called, did the output stay within the allowed range, did it respect the business rules. Same pattern, different domain.

Collapse
 
gramli profile image
Daniel Balcarek

Yep, putting AI behind the code really changes how we should think about both writing and testing it.

Did you also discover this the hard way, like I did? 😄 At first, I treated it almost like deterministic code. Then green tests started failing randomly and my AI endpoint was suddenly being used for completely unrelated topics. That was when I started realizing how differently these systems need to be designed and tested.

Collapse
 
xulingfeng profile image
xulingfeng

Yeah, we all learned it the hard way. Kinda miss the old days — test cases used to look so different before AI. But there's no going back now. Just gotta keep moving forward. 😅

Thread Thread
 
gramli profile image
Daniel Balcarek

AI still feels new to me, but when I read your reply, it sounds like you’re talking about the good old days. 😅

And actually, maybe you’re right. The AI hype has only been with us for about three years, but everything has changed so quickly that we can already say, “Back in the good old days…” 🤔😂

Collapse
 
hiper2d profile image
Aliaksei Zelianouski

One thing I'd add to the retry section: a retry that changes nothing just re-samples the same distribution. My version of your .HandleResult branch injects the failure into the next attempt - here's what you returned, here's the check it failed. And for a step that won't converge, I swap the model and rerun only that step. Keeping every step retryable in isolation means one bad generation never costs the whole run.

Collapse
 
gramli profile image
Daniel Balcarek

That’s actually a great point. The code in the article is only a simplified example, but this is an important insight that not everyone will realize immediately. I’m making a note and will update the article to include it. Thanks for mentioning it!

Collapse
 
ravipurohit1991 profile image
Ravi

The distinction between validating the request and validating the model’s response is useful. Checking the response structure is relatively straightforward, but a response can be perfectly valid JSON and still be logically wrong. How do you handle that case in practice? Do you retry, reject uncertain results, or send them for human review? That seems like the point where an AI endpoint differs most from a conventional one.

Collapse
 
gramli profile image
Daniel Balcarek

Great question, and I don’t think there is one universal answer, it really depends on the use case and the consequences of a wrong result.

If the failure is recoverable, I would usually retry and include the validation error in the next prompt, explaining exactly why the previous response was rejected. Otherwise, the model may simply repeat the same mistake.

After a predefined number of attempts, the next step depends on the domain. The endpoint can reject the result, fall back to a deterministic alternative, or send it for human review. For high-risk decisions, human review may be necessary. For lower-risk cases, rejecting the result can be enough, but the user should receive clear guidance about what failed and what they can change without exposing internal validation rules or sensitive implementation details.

Collapse
 
mightyblue profile image
Mightyblue

I'm nowhere near building AI endpoints — I'm a freelancer in Indonesia doing
cheap web work for small businesses, mostly using hosted models to fill content
pages. But the Ollama line caught me because I tried going local once, on a
budget PC, and the output was bad enough that I stopped.

Reading your retry section, I think I understand why now. I was treating the
bad output as a model problem. You're describing it as a flow problem —
validation after generation, then repair or fall back. The local model probably
needed more of that scaffolding, not less, which is the opposite of what I
assumed when I picked it to save money.

So the honest question from outside your world: is a small local model actually
cheaper once you count the validation layers it needs, or does the cost just
move from tokens to code you have to maintain?

Collapse
 
gramli profile image
Daniel Balcarek

Don’t give up on smaller models. they’re definitely usable. In many cases, it really is a flow problem rather than only a model problem. The backend needs to orchestrate the process and send smaller, specialized prompts instead of expecting one model call to handle everything.

I explored exactly that in this article: dev.to/gramli/how-to-use-gemma-4-e.... I ran Ollama with Gemma 4 E2B on a PC from 2015, and with the right orchestration, it worked surprisingly well.

As for the cost, I think it depends on the use case. Some of the cost may move from tokens to engineering and maintenance, but it doesn’t necessarily require a huge amount of extra code. I would start small and add only the validation, retry, or fallback steps that the endpoint actually needs. Just like with traditional APIs, not every endpoint needs the most complex possible flow.

Collapse
 
wrencalloway profile image
Wren Calloway

The retry section is where I'd push back on the framing. You treat retry as "consume more tokens, hope for better" — but the failure mode that actually bites is that a naive retry on a schema failure often reproduces the exact same wrong output, because the thing that made it fail (ambiguous instruction, a field the model can't infer from context) is still there. Retrying identical input against a probabilistic system isn't a coin flip toward correct; it's a coin flip weighted by the same prompt. If the temperature is low it can be nearly deterministic in its wrongness.

What changes the odds is feeding the validation error back in — "you returned X, the schema requires Y, the address field was empty" — so the second call has strictly more information than the first. That's repair, not retry, and it's worth separating them explicitly in your flow diagram because they have completely different success curves and cost profiles. A blind retry is the one that quietly burns tokens for nothing.

One more thing on the ID-mapping trick: it's a real cost win, but watch the failure it introduces — the model can hallucinate IDs that aren't in your input set. So that output still needs validation against the source list, otherwise you've traded "wrong object" for "confidently references record 47 that doesn't exist."

Collapse
 
gramli profile image
Daniel Balcarek

Thanks for the great insights! You're absolutely right about the retry vs. repair distinction. You're actually the third person to point it out, which tells me it's an important concept that isn't obvious at first glance. I'll definitely update the article to make that distinction explicit.

And yes, the ID mapping trick also needs validation against the original input set. Otherwise, you just replace one failure mode with another.

This is exactly why I enjoy publishing technical articles, the discussion often adds details that are hard to capture from a single perspective. Thanks for taking the time to write such a thoughtful comment!

Some comments may only be visible to logged-in visitors. Sign in to view all comments.