AlgoMaster Logo

Modeling Actions and State Transitions

High Priority11 min readUpdated September 13, 2026
Listen to this chapter
Unlock Audio

Some changes are straightforward to complete; others depend on work already happening elsewhere. In a bookstore, a customer might release a stock reservation after abandoning checkout or request cancellation of an order that a warehouse is already packing. Both operations change state, but they need different contracts: releasing a hold may finish immediately, while cancellation may require a decision from another system.

Modeling actions means describing what callers may request, when the request is valid, and what success actually guarantees.

This chapter uses a fictional bookstore to explain state transitions, action endpoints, and resources that track business requests.

1. States, Actions, and Transitions

A state describes a meaningful condition of a resource, such as an active reservation. An action expresses an intention, such as releasing that reservation. A transition is an allowed move from one state to another.

These concepts are related, but they are not interchangeable. The action release asks the service to stop holding stock. The resulting state released records the outcome. A client should not need to coordinate the internal stock updates that make the outcome true.

For this bookstore, a reservation begins as active after the service secures stock. Checkout can then consume it, its owner can release it, or the reservation can expire when its deadline passes. Those three outcomes are terminal: this model does not reactivate an ended hold.

The diagram labels each transition with the event that causes it:

The missing arrows matter. Checkout cannot consume an expired reservation, and releasing a consumed reservation does not cancel the resulting order. Each transition has a specific business meaning.

The resource keeps its identity across these changes. The owner can inspect /reservations/res_731 after release because the service retains ended reservations for 24 hours after they end. Ending the hold and deleting its record are separate operations.

2. Transition Rules

A state diagram describes possible movement, but an implementation needs more than arrows. A guard is a condition that must hold before a transition can happen. An effect is work the service performs as part of that transition.

For the reservation model, the rules are:

Scroll
TransitionInitiatorGuardRequired effect
Active to releasedReservation ownerHold remains active and unexpiredRelease its stock allocation and record the outcome
Active to consumedCheckout serviceHold remains valid and belongs to this checkout's customerTransfer the held allocation into the confirmed purchase
Active to expiredService clock or expiry processingDeadline passes before another terminal transitionEnd the hold and make its allocation available again

Ownership, time, and current state all participate in the decision. A customer having permission to read a reservation does not necessarily have permission to consume it. The service derives the caller's identity from authentication, not from an actor_id the caller supplies in the action body.

The deadline is a business condition, not merely a scheduler event. If expiry processing runs late, checkout must still reject a hold whose deadline has passed. A delayed background job must not extend a customer's guarantee accidentally.

Define invariants, conditions that remain true across all valid transitions. Here, the service must not both consume and release the same hold, or return its stock allocation twice. These promises determine what the implementation must coordinate.

Avoid exposing every internal processing step as a public state. Acknowledging queued work or acquiring a lock usually does not help a customer decide what to do. Expose an intermediate state when it changes the consumer's choices or the guarantees the API can make.

3. Choosing an Action Model

There are three common ways to expose a change. Choose based on its meaning and the information consumers need to retain.

Scroll
DesignExampleUseful when
Update a resource fieldChange a reading list's visibility through PATCHThe request naturally expresses an editable value
Expose a named actionPOST /reservations/res_731/releaseThe caller requests a business operation on an existing resource
Create an action resourcePOST /orders/ord_204/cancellationsThe request needs its own identity, status, inputs, or history

An editable status field is not inherently wrong. For example, an API can support publishing a draft through a validated field update. It must still enforce permissions, allowed transitions, and required effects.

The intentionally flawed design is a generic update that accepts any status string and saves it without those checks. Accepting {"status":"consumed"} must not let a customer skip checkout or claim stock after expiry. Changing a field in storage is insufficient when the state represents a business guarantee.

A named action makes that intention explicit. The bookstore chooses /release as a fixed action path and uses POST to request the operation. A colon suffix such as /reservations/res_731:release is another API convention. HTTP requires neither spelling. Pick one convention and make it consistent across the service.

An action resource adds something different: a separately inspectable request. An order cancellation may contain a reason, a requester, a decision, and completion timestamps. Giving it an identity helps consumers distinguish “my cancellation request exists” from “the service has cancelled my order.”

Do not manufacture a separate resource for every small action. If release completes immediately and the reservation itself provides all needed outcome information, a named action can be sufficient. Conversely, do not hide a substantial approval workflow behind a Boolean field merely to avoid an action endpoint.

4. A Reservation Release Contract

For this example, release is immediate. An authorized owner submits an empty JSON object. The action accepts no client-selected state, actor, or completion timestamp. Unknown input fields fail validation.

The examples use HTTP/1.1 over HTTPS at https://api.bookstore.example. EXAMPLE_TOKEN is a nonfunctional credential placeholder. Each JSON body occupies one line without a trailing newline.

Assume res_731 is active and expires at 14:10. At 14:05, its owner sends:

After the service ends the hold and releases its allocation, it returns the updated reservation:

The expiry field retains the original deadline; it does not mean the released hold remains usable until then. The status and release timestamp describe what actually happened. The service created no new API resource, so this operation returns an updated representation rather than a creation response.

The release action changes business state, so it must not run through GET. A read of the reservation should only report the outcome. Likewise, DELETE would be a poor fit for this contract because the reservation remains available for inspection.

An authorized repeat of this same empty request returns the current released representation without releasing stock again. The original released_at remains unchanged. That repeat behavior is an explicit promise of this action; POST does not supply it automatically.

5. Invalid and Conflicting Transitions

Specify outcomes for states where an action cannot proceed. “Release failed” is too vague for a client deciding whether to retry, refresh the resource, or ask the customer to do something else.

For this release contract:

Scroll
ConditionResponse choiceBehavior
Active and unexpired, caller is owner200 OKRelease the hold and return the updated reservation
Already released, caller is owner200 OKReturn the existing outcome without repeating effects
Already consumed or expired, caller is owner409 ConflictDo not change the terminal state
Body contains unsupported fields422 Unprocessable ContentDo not execute the action
Authenticated support viewer can read but cannot release403 ForbiddenLeave the hold unchanged
Reservation unavailable under the caller's visibility policy404 Not FoundDo not disclose a private reservation

The support-viewer example assumes the service deliberately grants read-only access. A different customer's private hold is subject to the visibility policy instead. These are application choices about error handling and disclosure, not a universal ordering of checks.

For a concrete conflict, assume checkout consumed a different reservation, res_732. Its owner sends:

The service reports the state conflict:

Retrying this action cannot turn the consumed hold into a released one. If the customer wants to undo the purchase, that requires the order's cancellation rules. The error should not suggest that repeating the same request will solve a terminal-state conflict.

A lost response is different from a rejected transition. If the owner does not know whether release succeeded, they can inspect the reservation or repeat the release under its documented repeat behavior. Keep outcome uncertainty separate from a known business rejection.

6. Competing Actions

A client may read active just before checkout consumes the reservation. A release request based on that observation must still check the authoritative state when it executes.

Reading the status, deciding in application code, and writing later without protection leaves a race. Two callers could both observe active and each apply a different terminal transition.

This diagram assumes the hold has not expired and shows a single guarded decision between competing actions:

Both terminal outcomes cannot win for the same hold. A database transaction or conditional update may enforce the decision, depending on the implementation. The state change and allocation handling must preserve the stock invariant together.

If that coordination spans services, do not assume a status update makes all remote effects atomic. The service may need a pending state and a durable record of unfinished work so it can finish or recover the remaining steps. Return released only when the service can honor the release guarantee it documents.

Some effects are secondary. An email notification can follow a completed release without making delivery of that email part of the release guarantee. Record enough information to retry such work without returning the allocation again. Distinguishing the required business effect from follow-up work keeps the public state meaningful.

7. Actions with Their Own Lifecycle

Order cancellation illustrates why an action sometimes deserves its own resource. Assume the service has confirmed an order, but the fulfillment service must decide whether packing has progressed too far to stop it.

The customer creates a cancellation request with a reason:

The bookstore saves the cancellation request durably and returns:

The created resource is the cancellation request. Its creation is complete, which is why this contract uses 201; cancelling the order is still pending. An API that instead accepts an action for later execution can use 202 Accepted and provide a way to inspect its outcome. Neither response alone means the service has already cancelled the order.

The cancellation request and order have related but separate lifecycles:

The customer can retrieve the cancellation resource at the returned Location to distinguish pending, succeeded, and rejected. A rejected request remains inspectable with its reason. If the service cannot contact fulfillment, it keeps the request pending while retrying. A worker error alone must not make the service report that the business rejected the cancellation.

Define what success includes. In this model, cancellation success means fulfillment has stopped and the service has cancelled the order. Any required refund has its own status; cancellation success does not claim the customer has already received money.

This API allows at most one cancellation request per order and retains it with the order. An authorized matching repeat returns that existing request with 200 OK; a different reason after submission produces a conflict rather than silently replacing the record. The service must enforce this rule under concurrent submissions. If the product later needs multiple attempts or appeals, it needs an explicit model for those requests.

Summary

Model actions around business intentions and allowed transitions. Define the starting state, permissions, guards, required effects, and observable outcome before choosing an endpoint shape.

Use field updates for suitable editable state, named actions for explicit operations, and separate resources when requests need their own identity and lifecycle. Handle repeats, competing transitions, and pending work so a reported state remains a dependable description of what the service has accomplished.