---
title: "Orleans.Lattice.Api.State.Grpc"
url: "https://nsta1.github.io/Orleans.Lattice/docs/lattice.api.state.grpc/README.html"
source: "https://github.com/NSTA1/Orleans.Lattice/blob/release/9.9/docs/lattice.api.state.grpc/README.md"
package: "Orleans.Lattice.Api.State.Grpc"
version: "9.9.0"
documents: "Orleans.Lattice 9.9.0 (release line 9.9)"
built: "2026-10-04"
all-pages: "https://nsta1.github.io/Orleans.Lattice/llms.txt"
bundle: "https://nsta1.github.io/Orleans.Lattice/docs/lattice.api.state.grpc/llms-full.txt"
---
# Orleans.Lattice.Api.State.Grpc

Part of the [documentation map](../index.md).

Code-first gRPC binding for [Orleans.Lattice.Api.State](../lattice.api.state/README.md) - projects the read-only state-API facade onto a long-lived gRPC service and a public typed client, over Orleans-serialized C# records that wrap or reuse facade DTOs, with no hand-written `.proto`.

## What is it?

`Orleans.Lattice.Api.State.Grpc` is the remote transport for the cluster state API. Hosts reference it when a dashboard, a CLI explorer, or the `Orleans.Lattice.Api.Mcp` MCP server in its remote topology needs to reach the read-only surface over the network rather than in-process.

It provides:

- **A code-first gRPC service.** Unary RPCs for the remotely supported read-only facade operations, including dead-letter count and listing, plus two server-streaming subscriptions (change and metric observation) and an unauthenticated auth-scheme advertisement RPC, all bound from C# definitions rather than a `.proto`.
- **A public typed client.** `LatticeStateApiGrpcClient` exposes one method per RPC over a caller-supplied gRPC channel.
- **Shared Orleans marshalling.** Every message is a `[GenerateSerializer]` C# record, serialized with the Orleans binary serializer; gRPC-specific envelopes wrap scalar facade arguments or results where needed, so client and server stay in lock-step by construction.
- **Fail-closed authorization.** A per-call `ILatticeStateApiAuthorizer` seam gates every protected RPC; the default denies protected traffic until configured. `GetAuthScheme` is unauthenticated for scheme discovery.

The package is **read-only** and has no external broker and no `.proto` file to maintain.

## Core Properties

- **Read-only by construction.** The service exposes observation verbs only - discovery, structure, entries, change feeds, and metrics.
- **Public client, internal service.** Callers consume `LatticeStateApiGrpcClient`; the service, marshallers, and method definitions are internal.
- **No transport policy in the client.** Address, TLS, retries, deadlines, and credentials live on the caller's `GrpcChannel` / `CallInvoker`.
- **Fail-closed.** Unconfigured, the binding denies every protected call rather than serving state unauthenticated; only `GetAuthScheme` remains open to advertise sign-in options.

## Features

| Feature | What it gives you | Docs |
|---|---|---|
| **Code-first service** | Unary and server-streaming RPCs bound from C# - no `.proto` to author or keep in sync. | [gRPC Contract](../lattice.api.state/grpc-contract.md) |
| **Public typed client** | `LatticeStateApiGrpcClient` over a caller-supplied channel, one method per RPC. | [Client](../lattice.api.state/client.md) |
| **Fail-closed authorization** | Per-call `ILatticeStateApiAuthorizer` seam, default-deny. | [Security](../lattice.api.state/security.md) |
| **Credential bridge and sign-in discovery** | `LatticeStateApiGrpcOptions` names the request header (`CredentialHeaderName`, default `authorization`) and scheme (`CredentialScheme`, default `Bearer`) bridged onto the ambient Lattice credential, and the `AdvertisedAuthSchemes` (empty by default) the unauthenticated `GetAuthScheme` RPC returns. | [Configuration](../lattice.api.state/configuration.md#latticestateapigrpcoptions) |

## Quick Start

Register the binding on a silo that already has `AddLatticeStateApi`, then map its routes:

```csharp verify
var builder = WebApplication.CreateBuilder();
builder.Services.AddLatticeStateApiGrpc(o => o.RequireAuthorization = true);
builder.Services.AddSingleton<ILatticeStateApiAuthorizer, AllowAllStateApiAuthorizer>();

var app = builder.Build();
app.MapLatticeStateApiGrpc();
```

See the [`Orleans.Lattice.Api.State` overview](../lattice.api.state/README.md) for the full setup, surfaces, security, and client documentation, and the [`StateExplorer`](../../samples/StateExplorer/README.md) sample for a runnable end-to-end journey.

### Per-tenant selection

On a cluster running the optional tenancy add-on, the call's *active tenant* scopes the tree namespace the call reads. The binding lifts it from a single request header - `lattice-active-tenant` by default, configurable through `LatticeStateApiGrpcOptions.ActiveTenantHeaderName` - and stamps it onto the call's ambient scope, so the facade resolves each request's tree name into that tenant's `t/{tenant}/{name}` namespace and its catalog enumerations return only that tenant's trees:

```csharp verify
var builder = WebApplication.CreateBuilder();
builder.Services.AddLatticeStateApiGrpc(o =>
{
    o.RequireAuthorization = true;
    o.ActiveTenantHeaderName = "lattice-active-tenant";
});
```

The header carries only an *assertion*: the tenancy add-on re-validates it against the caller's subject membership downstream, exactly as it validates the caller credential. An absent, blank, or syntactically invalid header asserts no tenant, so the call resolves the default tenant and its tree names are used unchanged - existing tenant-unaware clients keep working. An asserted tenant the caller may not act as (any assertion by an anonymous caller included) is refused: a call that addresses a tree surfaces as a `PermissionDenied` `RpcException`, as does naming a `sys-` tree or a malformed `t/` id under an asserted tenant, while a catalog enumeration is pruned to the asserted tenant's own trees plus platform-owned ones and, with the default auth-backed read visibility, re-authorized entry by entry - so such a caller sees none of that tenant's trees, never the cluster-global catalog. Set the option to an empty string to disable header-based tenant selection entirely. With no tenancy add-on registered an asserted tenant changes nothing - tree names resolve unchanged - so the binding behaves exactly as it did before tenancy existed.

## Reference

- [gRPC Contract](../lattice.api.state/grpc-contract.md) - the service, the RPCs, and the wire records.
- [Client](../lattice.api.state/client.md) - building and driving `LatticeStateApiGrpcClient`.
- [Security](../lattice.api.state/security.md) - the authorization seam and transport story.
- [Setup](../lattice.api.state/setup.md) - registration and route mapping.
- [Configuration](../lattice.api.state/configuration.md) - every `LatticeStateApiGrpcOptions` and `EnvVarCredentialAuthorizerOptions` property, its type, and its default.
