DEV Community

Cover image for Your API Returned 200 OK. Your AI Agent Still Failed.
Sudhanshu Thakur
Sudhanshu Thakur

Posted on

Your API Returned 200 OK. Your AI Agent Still Failed.

Why successful API calls are no longer enough when AI agents can take real-world actions

For most backend systems, 200 OK is comforting.

It means the request reached the server, passed validation, and completed successfully.

For an AI agent, however, 200 OK can hide one of the most dangerous failure modes in modern software:

The API did exactly what the agent asked — but the agent asked for the wrong thing.

Imagine an AI-powered banking assistant.

A customer says:

“Refund the duplicate payment from yesterday.”

The agent retrieves several transactions, identifies what it believes is the duplicate, and calls:

POST /refunds
Enter fullscreen mode Exit fullscreen mode

The request is authenticated.

The agent is authorised.

The transaction ID exists.

The refund API processes the request successfully.

HTTP/1.1 200 OK
Enter fullscreen mode Exit fullscreen mode

Every technical dashboard is green.

But the agent selected the wrong transaction.

The API succeeded.

The business outcome failed.

As AI systems evolve from chatbots that recommend actions into agents that execute them, backend engineers need to rethink what “success” actually means.


The Problem: We Treat Success as a Single Layer

Traditional systems usually measure success at several technical levels.

At the network layer:

Did the request reach the service?
Enter fullscreen mode Exit fullscreen mode

At the API layer:

Did the service return a successful response?
Enter fullscreen mode Exit fullscreen mode

At the database layer:

Did the transaction commit?
Enter fullscreen mode Exit fullscreen mode

That works reasonably well when deterministic application code has already decided what operation should happen.

For example:

refundService.refund(transactionId);
Enter fullscreen mode Exit fullscreen mode

The developer has chosen:

  • which service to call,
  • which transaction to target,
  • when the call should happen,
  • what should happen after it succeeds.

Agentic systems change this relationship.

An AI agent may be given tools such as:

findCustomer()
lookupTransaction()
issueRefund()
cancelOrder()
sendEmail()
disableAccount()
restartService()
Enter fullscreen mode Exit fullscreen mode

The model then determines:

Which tool should I call?
Which parameters should I use?
Should I retry?
What should I do next?
Enter fullscreen mode Exit fullscreen mode

We have introduced probabilistic reasoning before deterministic side effects.

That means we need another definition of success.


Three Levels of Success

I find it useful to separate agent success into three layers.

1. Transport Success

Did the technical request complete?

HTTP/1.1 200 OK
Enter fullscreen mode Exit fullscreen mode

2. Execution Success

Did the backend perform the requested operation?

Refund created successfully.
Enter fullscreen mode Exit fullscreen mode

3. Intent Success

Did the system perform the right action, on the right resource, for the right user, under the right conditions, exactly as intended?

For a refund that might mean:

Correct customer
Correct transaction
Correct amount
Correct reason
Correct approval
Exactly once
Enter fullscreen mode Exit fullscreen mode

The first two are familiar engineering problems.

The third becomes much more important once AI starts selecting and sequencing actions dynamically.


Failure Mode 1: The API Correctly Executes the Wrong Decision

Consider this user request:

“Refund the most recent duplicate charge.”

The agent receives:

[
  {
    "id": "TX-18419",
    "amount": 2500,
    "merchant": "ABC Store"
  },
  {
    "id": "TX-18491",
    "amount": 2500,
    "merchant": "ABC Store"
  }
]
Enter fullscreen mode Exit fullscreen mode

The agent incorrectly chooses:

TX-18491
Enter fullscreen mode Exit fullscreen mode

and sends:

{
  "transactionId": "TX-18491",
  "amount": 2500
}
Enter fullscreen mode Exit fullscreen mode

The backend validates the request.

The account has sufficient authority.

The transaction exists.

The refund executes.

Technically, there is no error.

But the customer wanted another transaction refunded.

This is an important distinction:

API correctness does not guarantee semantic correctness.

The service knows how to refund a transaction.

It does not necessarily know whether the AI chose the correct transaction.


Failure Mode 2: The Agent Retries Something That Already Worked

Now imagine the refund really is correct.

The agent calls the service.

Agent → Refund API
Enter fullscreen mode Exit fullscreen mode

The refund succeeds.

But the response is lost.

The agent sees:

Timeout
Enter fullscreen mode Exit fullscreen mode

It reasons:

“The refund probably failed. I should retry.”

The second call also succeeds.

Without protection, one user intent may produce multiple real-world side effects.

This problem is familiar to payment engineers and distributed-systems developers.

The difference is that with autonomous agents, retries may not come from a predefined retry library.

The model itself can decide:

“Let me try that again.”

That makes idempotency even more important.


Give Every Important Action an Intent ID

We already use identifiers such as:

request_id
trace_id
span_id
Enter fullscreen mode Exit fullscreen mode

Those identify technical execution.

Agents also need something representing the business objective.

For example:

intent_id = REFUND_DUPLICATE_CHARGE_8472
Enter fullscreen mode Exit fullscreen mode

One intent may generate many technical requests:

REFUND_DUPLICATE_CHARGE_8472
        |
        +-- lookup transaction
        |
        +-- validate eligibility
        |
        +-- create refund
        |
        +-- update CRM
        |
        +-- notify customer
Enter fullscreen mode Exit fullscreen mode

Suppose the refund API times out.

Instead of asking:

Should I POST /refunds again?
Enter fullscreen mode Exit fullscreen mode

the system can ask:

Has REFUND_DUPLICATE_CHARGE_8472
already produced a successful refund?
Enter fullscreen mode Exit fullscreen mode

That is a much safer abstraction.


A Simple Intent Model

Conceptually:

public record AgentIntent(
    UUID intentId,
    String userId,
    String action,
    String resourceId,
    IntentStatus status,
    String resultId
) {}
Enter fullscreen mode Exit fullscreen mode

With:

public enum IntentStatus {
    PENDING,
    EXECUTING,
    COMPLETED,
    REQUIRES_REVIEW,
    FAILED
}
Enter fullscreen mode Exit fullscreen mode

Before executing a mutation:

AgentIntent intent = intentRepository.findById(intentId)
    .orElseThrow();

if (intent.status() == IntentStatus.COMPLETED) {
    return previousResult(intent.resultId());
}
Enter fullscreen mode Exit fullscreen mode

The exact implementation will vary.

The principle is what matters:

A retry should refer to the same business intent instead of silently becoming a new action.


Failure Mode 3: Every Tool Succeeds, but the Workflow Is Wrong

Imagine an account-closing agent.

It successfully executes:

✓ Cancel subscription
✓ Revoke API credentials
✓ Delete files
✓ Generate final invoice
✓ Close account
Enter fullscreen mode Exit fullscreen mode

Every API returns success.

But company policy requires:

Export compliance archive
BEFORE
Delete files
Enter fullscreen mode Exit fullscreen mode

The agent skipped the archive.

Five green tool calls.

One invalid business process.

This is why agent observability cannot stop at:

Tool call succeeded
Enter fullscreen mode Exit fullscreen mode

We need to ask:

Was the workflow itself valid?
Enter fullscreen mode Exit fullscreen mode

Put a Deterministic Gate Between Reasoning and Mutation

For read-only operations, directly exposing tools may be reasonable.

For high-impact actions, I prefer an architecture like:

User Goal
   ↓
AI Agent
   ↓
Intent + Policy Gate
   ↓
Tool Gateway
   ↓
Business API
   ↓
Outcome Verification
Enter fullscreen mode Exit fullscreen mode

Before issuing a refund, deterministic code can verify:

transaction belongs to authenticated user
AND transaction is refundable
AND amount <= remaining refundable amount
AND approval threshold is satisfied
AND intent has not already completed
Enter fullscreen mode Exit fullscreen mode

The model proposes.

The system verifies.

The API executes.

The system verifies again.

That separation is important.


Authorised Does Not Mean Appropriate

Security controls still matter enormously.

But authorisation alone does not solve every agent problem.

Suppose an agent legitimately has permission to call:

restartProductionService()
Enter fullscreen mode Exit fullscreen mode

The credentials are valid.

The operator has the correct role.

But should the service restart now?

Perhaps:

a deployment is currently running
Enter fullscreen mode Exit fullscreen mode

or:

an incident is already active
Enter fullscreen mode Exit fullscreen mode

or:

traffic is at its daily peak
Enter fullscreen mode Exit fullscreen mode

or:

another restart happened 30 seconds ago
Enter fullscreen mode Exit fullscreen mode

Authentication answers:

Who are you?

Authorisation answers:

Are you allowed to perform this operation?

Agentic systems also need:

Is this action appropriate in the current context?

That requires policy, state, and sometimes human judgement.


Define Preconditions and Postconditions

Many tools are described approximately like this:

name: issue_refund
description: Refund a customer transaction
Enter fullscreen mode Exit fullscreen mode

That tells the model what the tool does.

It does not define the conditions that make using it safe.

A stronger contract might be:

tool: issue_refund

preconditions:
  - transaction belongs to authenticated customer
  - transaction is refundable
  - amount <= remaining refundable balance

execution:
  idempotency_required: true

postconditions:
  - refund record exists
  - refund references expected transaction
  - refund amount matches approved amount
  - ledger state reconciles

approval:
  required_above: 5000
Enter fullscreen mode Exit fullscreen mode

Now success is not simply:

function returned successfully
Enter fullscreen mode Exit fullscreen mode

It becomes:

preconditions satisfied
+
action executed
+
postconditions verified
Enter fullscreen mode Exit fullscreen mode

Don’t Let the Agent Grade Its Own Homework

A tempting pattern is:

Agent performs action
↓
Agent asks itself:
"Did that work?"
↓
Agent continues
Enter fullscreen mode Exit fullscreen mode

For low-risk workflows, this may be sufficient.

For important actions, it is fragile.

If the model misunderstood the original request, asking the same model whether its interpretation was correct may reproduce the same mistake.

High-impact actions should be verified against external evidence:

database state
payment receipt
ledger state
policy engine
independent validator
sensor state
human approval
Enter fullscreen mode Exit fullscreen mode

The model can reason about these signals.

It should not invent them.


Observability Needs to Move Above HTTP

Imagine this dashboard:

refund-api availability:       99.99%
tool-call success rate:        98.9%
average API latency:           310 ms
Enter fullscreen mode Exit fullscreen mode

Everything appears healthy.

But you are not measuring:

wrong-target actions
duplicate mutations
policy violations
unverified outcomes
human reversals
Enter fullscreen mode Exit fullscreen mode

Agentic systems need higher-level metrics.

For example:

verified_outcome_rate
duplicate_action_rate
postcondition_failure_rate
human_override_rate
ambiguous_outcome_rate
intent_reconciliation_rate
Enter fullscreen mode Exit fullscreen mode

A meaningful future SLO might look like:

99.95% of high-impact agent intents complete with a verified business outcome and no duplicate side effect.

That tells us far more than API availability.


A Safer End-to-End Example

Suppose a user tells an AI commerce agent:

“Cancel my duplicate order and refund it.”

Instead of immediately executing actions, the workflow could be:

1. Create intent
   CANCEL_DUPLICATE_ORDER_9821

2. Retrieve candidate orders

3. Deterministically verify:
   - same customer
   - duplicate item
   - matching amount
   - cancellable state

4. Generate action preview:
   Cancel Order A1842
   Refund £74.99

5. Request human confirmation if required

6. Cancel order using intent ID

7. Issue refund using same intent context

8. Verify:
   order == CANCELLED
   refund == CONFIRMED
   refund amount == £74.99

9. Mark intent COMPLETED

10. Tell user:
    "Done"
Enter fullscreen mode Exit fullscreen mode

Step 10 is the important part.

The agent does not say “done” because it received 200 OK.

It says “done” because the system verified the intended business outcome.


Six Controls I’d Use for High-Impact Agent Actions

For any agent capable of moving money, modifying production systems, changing permissions, deleting information, or performing irreversible actions:

1. Explicit intent

Persist the actual business objective.

2. Least-privilege tools

Expose only capabilities required for the task.

3. Deterministic preconditions

Keep critical business rules outside the model.

4. Idempotent mutations

Design retries so repeating a request does not repeat the side effect.

5. Independent postcondition verification

Verify the resulting state using trusted systems.

6. Outcome-level auditability

Connect:

user intent
→ agent decision
→ policy result
→ tool call
→ API response
→ verified business state
Enter fullscreen mode Exit fullscreen mode

That gives us:

Intent
  ↓
Policy
  ↓
Action
  ↓
Receipt
  ↓
Verification
  ↓
Outcome
Enter fullscreen mode Exit fullscreen mode

instead of:

Prompt
  ↓
Tool
  ↓
200 OK
  ↓
"Done!"
Enter fullscreen mode Exit fullscreen mode

Agent-Facing APIs May Need Richer Contracts

Traditional APIs mainly answer:

What operation can I call?
What arguments are required?
What response will I receive?
Enter fullscreen mode Exit fullscreen mode

AI-agent-facing capabilities may need to expose more:

What risk level does this action carry?
Is the operation reversible?
Does it require approval?
Can it be retried safely?
What preconditions must hold?
What proves successful completion?
Enter fullscreen mode Exit fullscreen mode

That turns the API from a simple interface into something closer to a capability contract.

The AI supplies flexible reasoning.

The surrounding system supplies deterministic guarantees.


Final Thought

We are investing enormous effort in making AI agents smarter.

Better models.

More context.

More tools.

Longer workflows.

Greater autonomy.

But once an agent can modify the real world, the hardest production question may not be:

Can the model determine what to do?

It may be:

How do we prove that what it just did was actually what the user intended?

The most dangerous failure may never generate an exception.

It may not trigger PagerDuty.

It may not appear in the error logs.

Every service may remain healthy.

All you may see is:

HTTP/1.1 200 OK
Enter fullscreen mode Exit fullscreen mode

The API succeeded.

The AI agent completed its task.

And the business still lost.

That is why production Agentic AI needs more than successful tool calls.

It needs verified outcomes.


References and Further Reading

  • OWASP — LLM06: Excessive Agency: guidance on risks caused by excessive functionality, permissions, and autonomy in LLM applications.
  • OWASP — Agentic AI Security: emerging guidance for autonomous AI applications and tool-enabled agents.
  • NIST — AI Agent Identity and Authorization: work examining how established identity and authorisation practices apply to software and AI agents.
  • AWS Well-Architected Framework — Idempotent Mutating Operations: guidance for making retries safe in distributed systems.
  • Stripe API Documentation — Idempotent Requests: a practical example of retry-safe financial API mutations.

Top comments (1)

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