---
title: "RPC"
description: "An RPC (Remote Procedure Call) lets code outside a Flow interact with a"
canonical_url: "https://docs.superdurable.io/concepts/primitives/rpc/"
source: "docs/content/concepts/primitives/rpc.mdx"
locale: "en"
---

# RPC

An **RPC** (Remote Procedure Call) lets code outside a Flow interact with a
running Flow. A web handler, another service, or an operator tool
calls it through a [Client](https://docs.superdurable.io/concepts/client.md).

A **Client** invokes an RPC. **Dex Server** sends it to a **Worker**. The
**Worker** runs it, then returns the result to **Dex Server**, which saves it. **Dex
Server** then returns the output to the **Client**.

A Client sends an RPC request to Dex Server, which routes it to the Application Worker. The Worker returns the result through Dex Server to the Client.

## When to use an RPC

Use an RPC when something outside the Flow must change it or ask it a question.
Examples are an approval, a user who presses Stop, or a request for a view of
the Flow's state.

An RPC handler can do four things:

- read and write [Attributes](https://docs.superdurable.io/concepts/primitives/attribute.md);
- publish messages to a [Channel](https://docs.superdurable.io/concepts/primitives/channel.md);
- start new [Steps](https://docs.superdurable.io/concepts/step.md);
- cancel running Steps.

<a id="read-only-rpcs-and-terminal-flows"></a>

For a plain read, a Client can skip the RPC. See
[when a read needs an RPC](https://docs.superdurable.io/concepts/client.md#related-primitives). For RPCs that
only read, including on closed Flows, see [Read-only RPCs and terminal Flows](https://docs.superdurable.io/reference/server/runtime-data.md#read-only-rpcs-and-terminal-flows).

## Guarantees

Dex saves an RPC like a Step method. It saves the RPC's Attribute writes and
Channel publications together with its result, and only when the RPC succeeds.
See [Step guarantees](https://docs.superdurable.io/concepts/step.md#guarantees).

An RPC handler reads a snapshot of the Flow. Steps and other RPCs can change the
Flow between that read and the moment Dex saves the RPC's changes. A
transactional RPC does not change this. When the RPC's change depends on what it read, declare a lock on that
Attribute in this RPC and in every Step and RPC that writes it. Dex then runs
them one at a time. If the Flow closes while the handler runs, Dex rejects the
RPC's changes. The handler's calls to external systems have still happened.

Each RPC call from an SDK is a new request. If you retry a call, Dex can run the
handler again and save its changes a second time. Make RPC handlers safe to
repeat.

<a id="rpc-timeout"></a>

An RPC runs on a Worker, like a Step. A timeout limits each handler invocation.
See [RPC timeout](https://docs.superdurable.io/reference/server/runtime-data.md#rpc-timeout).

## Define and invoke an RPC

This RPC writes an Attribute, publishes a Channel message, and starts a Step:

**Python**

```python
@rpc
def trigger(self, context: Context, input: str) -> RPCResult[str]:
    self.data.set(context, input)
    self.example_ch.publish(context, None)
    return RPCResult(input, next_steps=(StepMovement.of(Example, input),))
```

Example: [examples/python/dex_examples/primitives/rpc](https://github.com/superdurable/dex/tree/main/examples/python/dex_examples/primitives/rpc)

**Go**

```go
func (flow *RpcFlow) GetRPCs() []dex.RPCDef {
	return []dex.RPCDef{
		dex.DefineRPC(flow.Trigger, &dex.RPCOptions{Timeout: 30 * time.Second}),
	}
}

func (*RpcFlow) Trigger(ctx dex.Context, input string) (*dex.RPCResult[string], error) {
	if err := Data.Set(ctx, input); err != nil {
		return nil, err
	}
	if err := ExampleCh.Publish(ctx, nil); err != nil {
		return nil, err
	}
	return &dex.RPCResult[string]{
		Output: input,
		NextSteps: []dex.StepMovement{
			dex.MovementOf(exampleStep{}, input),
		},
	}, nil
}
```

Example: [examples/go/primitives/rpc](https://github.com/superdurable/dex/tree/main/examples/go/primitives/rpc)

**Java**

```java
@RPC
public RPCResult<String> trigger(final Context context, final String input) {
    data.set(context, input);
    exampleCh.publish(context, null);
    return RPCResult.of(input, StepMovement.of(Example.class, input));
}
```

Example: [examples/java/src/main/java/io/superdurable/dex/primitives/rpc](https://github.com/superdurable/dex/tree/main/examples/java/src/main/java/io/superdurable/dex/primitives/rpc)

**TypeScript**

```typescript
@rpc({ inputCodec: stringCodec, outputCodec: stringCodec })
public trigger(context: Context, input: string): RPCResult<string> {
  this.data.set(context, input);
  exampleCh.publish(context, undefined);
  return { output: input, nextSteps: [StepMovement.of(Example, input)] };
}
```

Example: [examples/typescript/src/primitives/rpc](https://github.com/superdurable/dex/tree/main/examples/typescript/src/primitives/rpc)

**Rust**

```rust
impl RpcFlow {
    fn trigger(&self, context: &mut Context, input: String) -> HandlerResult<RpcResult<String>> {
        data().set(context, input.clone())?;
        example_ch().publish(context, ())?;
        Ok(RpcResult::new(input.clone()).then(StepMovement::to(&self.example_step, input)))
    }
}
```

Example: [examples/rust/src/primitives/rpc](https://github.com/superdurable/dex/tree/main/examples/rust/src/primitives/rpc)

Then invoke the RPC from a Client:

**Python**

```python
result = await client.invoke_rpc(flow.trigger, flow_id, message)
```

Example: [examples/python/dex_examples/primitives/rpc](https://github.com/superdurable/dex/tree/main/examples/python/dex_examples/primitives/rpc)

**Go**

```go
var result string
err := client.InvokeRPC(
	ctx,
	flowID,
	flow.Trigger,
	message,
	&result,
)
```

Example: [examples/go/primitives/rpc](https://github.com/superdurable/dex/tree/main/examples/go/primitives/rpc)

**Java**

```java
final RpcFlow flow = client.newRpcStub(RpcFlow.class, flowId);
final String result = client.invokeRPC(flow::trigger, message);
```

Example: [examples/java/src/main/java/io/superdurable/dex/primitives/rpc](https://github.com/superdurable/dex/tree/main/examples/java/src/main/java/io/superdurable/dex/primitives/rpc)

**TypeScript**

```typescript
const result = await client.invokeRPC(flow.trigger, flowId, message);
```

Example: [examples/typescript/src/primitives/rpc](https://github.com/superdurable/dex/tree/main/examples/typescript/src/primitives/rpc)

**Rust**

```rust
let result = client.invoke_rpc(&workflow_id, TRIGGER_RPC, message)?;
```

Example: [examples/rust/src/primitives/rpc](https://github.com/superdurable/dex/tree/main/examples/rust/src/primitives/rpc)

<a id="java-and-kotlin"></a>

<a id="typescript-codecs"></a>

Java, Kotlin, and TypeScript have extra RPC rules. See
[RPC stubs and Kotlin](https://docs.superdurable.io/reference/java/rpc.md#rpc-stubs-and-kotlin) and
[RPC codecs](https://docs.superdurable.io/reference/typescript/rpc.md#rpc-codecs).

<a id="cancel-running-steps"></a>

## Cancel running Steps

<a id="cancel-race"></a>

<a id="cancel-honour"></a>

An RPC result can cancel active Steps of its Flow, selected by Step type. Use it
when a request from outside makes work in progress pointless, such as a user who
presses Stop while a report or a model call runs. Dex applies the cancellation
when it saves the RPC's result. The Step code must check for cancellation and stop. See
[Cancel running Steps](https://docs.superdurable.io/reference/server/runtime-data.md#cancel-running-steps) for the example,
[Resolve the race from committed state](https://docs.superdurable.io/reference/server/runtime-data.md#cancel-race) for a Step that
finishes first, and [Make the Step stop](https://docs.superdurable.io/reference/server/runtime-data.md#cancel-honour) for how each SDK
delivers cancellation.

<a id="transactional-reads-and-writes"></a>

## Locks and transactional RPCs

<a id="attribute-locks"></a>

An RPC can declare [Attribute locks](https://docs.superdurable.io/concepts/primitives/attribute.md#locking).
It then runs one at a time with the Steps and RPCs that declare the same lock.
See [Locks](https://docs.superdurable.io/reference/server/runtime-data.md#attribute-locks) and
[RPC lock conflicts](https://docs.superdurable.io/reference/server/runtime-data.md#rpc-lock-conflicts).

A **transactional** RPC adds a check when Dex saves its result. If a Channel
message that it deletes is no longer pending, the RPC fails and Dex saves none
of its changes. A non-transactional RPC skips the missing deletion and Dex saves
the rest. An RPC
that holds a lock is always transactional. See
[Transactional RPCs](https://docs.superdurable.io/reference/server/runtime-data.md#transactional-reads-and-writes).

<a id="load-collection-state"></a>

<a id="runtime-selected-map-instances"></a>

## Load collections

An RPC receives regular Attributes and Channel sizes. It loads AttributeMap
entries and pending Channel messages only when it declares them. This keeps
large collections out of RPCs that do not use them. See
[Selective state loading](https://docs.superdurable.io/reference/server/runtime-data.md#load-collection-state) and
[Runtime-selected map instances](https://docs.superdurable.io/reference/server/runtime-data.md#runtime-selected-map-instances).

## Reference

The [RPC](https://docs.superdurable.io/reference/python/rpc.md) page of each SDK reference lists the
API names in that language.

[Data primitive semantics](https://docs.superdurable.io/reference/server/runtime-data.md#rpcs) covers
cancellation, read-only RPCs on closed Flows, state loading, transactional
RPCs, and timeouts in detail.
