dotnet-rivet
0.46.0
dotnet tool install --global dotnet-rivet --version 0.46.0
dotnet new tool-manifest
dotnet tool install --local dotnet-rivet --version 0.46.0
#tool dotnet:?package=dotnet-rivet&version=0.46.0
nuke :add-package dotnet-rivet --version 0.46.0
<p align="center"> <h1 align="center">Rivet</h1> <p align="center"> <a href="https://www.nuget.org/packages/Rivet.Attributes"><img src="https://img.shields.io/nuget/v/Rivet.Attributes?label=Rivet.Attributes" alt="NuGet" /></a> <a href="https://www.nuget.org/packages/dotnet-rivet"><img src="https://img.shields.io/nuget/v/dotnet-rivet?label=dotnet-rivet" alt="NuGet" /></a> <img src="https://img.shields.io/badge/license-MIT-blue" alt="License" /> </p> </p>
Your C# is the contract. Rivet reads your compiled C# with Roslyn and deterministically emits an OpenAPI 3.1 spec from its declared transport shape, with diagnostics for known fidelity loss. There is no runtime reflection and no need for attributes on every member. The OpenAPI ecosystem does the rest: TypeScript types, a typed fetch client, Zod schemas, rendered docs.
oRPC gives you this when your server is TypeScript. Rivet gives you the same DX when your server is .NET.
Prerequisites
- .NET 8 SDK or later for your API project (
Rivet.Attributestargets net8.0, net9.0 and net10.0). - .NET 9 or later for the
dotnet-rivettool: the SDK, or both the .NET and ASP.NET Core runtimes. - Node.js, only for the TypeScript steps under Consume.
Install
dotnet new webapi -n Api --use-controllers # or skip this and use an existing ASP.NET Core project
cd Api
dotnet add package Rivet.Attributes
dotnet tool install --global dotnet-rivet
Two ways in
Already have an ASP.NET API? Annotate it.
Mark the endpoints you want surfaced — the operation is derived from what you
explicitly declare (routes, [FromBody]/[FromQuery]/... bindings,
[ProducesResponseType] entries) plus a few documented narrow conventions, not
from a reconstruction of MVC's model-binding defaults:
using Microsoft.AspNetCore.Mvc;
using Rivet;
public sealed record TaskDetailDto(Guid Id, string Title);
public sealed record NotFoundDto(string Message);
[ApiController]
[Route("api/tasks")]
public sealed class TasksController : ControllerBase
{
[RivetEndpoint]
[HttpGet("{id:guid}")]
[ProducesResponseType(typeof(TaskDetailDto), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(NotFoundDto), StatusCodes.Status404NotFound)]
public IActionResult Get(Guid id) =>
id == Guid.Empty
? NotFound(new NotFoundDto("Task not found"))
: Ok(new TaskDetailDto(id, "Write the docs"));
}
That becomes GET /api/tasks/{id} with a typed 200 and 404 — route
constraints normalised, params classified, multipart and form bodies handled.
Starting fresh? Write the contract first.
A contract is plain C#: routes, inputs, outputs, and error responses in one place, as data:
using Rivet;
public sealed record MemberDto(Guid Id, string Email);
public sealed record PagedResult<T>(IReadOnlyList<T> Items, int TotalCount);
public sealed record InviteMemberRequest(string Email);
public sealed record InviteMemberResponse(Guid Id);
public sealed record ValidationErrorDto(string Message);
[RivetContract]
public static class MembersContract
{
public static readonly RouteDefinition<PagedResult<MemberDto>> List =
Define.Get<PagedResult<MemberDto>>("/api/members");
public static readonly RouteDefinition<InviteMemberRequest, InviteMemberResponse> Invite =
Define.Post<InviteMemberRequest, InviteMemberResponse>("/api/members")
.Status(201)
.Returns<ValidationErrorDto>(422, "Validation failed")
.Secure("admin");
}
At the transport boundary, bind the declared input, run ordinary application code,
then construct the response through the contract. The compiler enforces the input
and output types; Rivet validates the selected response at runtime. IMemberService
stands in for your application code; register an implementation before calling it:
using Microsoft.AspNetCore.Mvc;
using Rivet;
public interface IMemberService
{
Task<InviteMemberResponse> Invite(InviteMemberRequest request, CancellationToken ct);
}
[ApiController]
[Route("api/members")]
public sealed class MembersController(IMemberService memberService) : ControllerBase
{
[HttpPost]
public async Task<IActionResult> Invite(
[FromBody] InviteMemberRequest request,
CancellationToken ct
)
{
var endpoint = MembersContract.Invite.Bind(request);
var response = await memberService.Invite(request, ct);
// Must be InviteMemberResponse — compiler-enforced
return endpoint.Success(response).ToActionResult();
}
}
Either way — annotated endpoints, contracts, or a mix — the spec comes out the same.
Generate
dotnet rivet --project Api.csproj --output ./generated --security admin=bearer
Writes ./generated/openapi.json, derived from the compiled C# via the Roslyn
semantic model. Rivet reads explicit contract declarations and explicit ASP.NET
transport metadata, with deliberately tiny documented conventions — value-object
brands are opt-in via [RivetScalar], enums are numeric unless a type-level
JsonStringEnumConverter declares them string-valued. Generics, nullability,
validation attributes, polymorphic hierarchies (oneOf + discriminator),
dictionary key types, headers, descriptions, and examples all flow into the
spec.
admin=bearer defines the bearer scheme that .Secure("admin") names. The first
--security is also the document-wide default, so every endpoint without
.Anonymous() requires it, GET /api/tasks/{id} included. Drop the flag and the
.Secure("admin") call for an unauthenticated API, or see the
CLI reference for
multiple schemes.
Consume
The spec plugs straight into the OpenAPI TypeScript ecosystem:
npx openapi-typescript ./generated/openapi.json -o ./src/api/schema.d.ts
npm install openapi-fetch
import createClient from "openapi-fetch";
import type { paths } from "./api/schema";
const api = createClient<paths>({ baseUrl: "https://api.example.com" });
const taskId = "3fa85f64-5717-4562-b3fc-2c963f66afa6";
// Path, params, body, and per-status responses all inferred.
const { data, error } = await api.GET("/api/tasks/{id}", {
params: { path: { id: taskId } },
});
if (error) {
// narrowed to NotFoundDto for the declared 404
console.error(error.message);
}
Docs via any OpenAPI renderer; runtime validators via openapi-zod-client if you want them.
Also in the box
Azure Functions sample and integration guide — isolated-worker routes, per-response file MIME selection, and
task test:functionsagainst the real hostContract coverage checking —
--checkverifies every contract field has an implementation on the declared route and methodOpenAPI import — one-shot onboarding for existing APIs: generate C# contracts from a spec, then the C# is the source of truth
File endpoints, headers as contract concepts, minimal-API hosts, round-trippable specs
Stable
RIVnnnndiagnostic IDs on every warning — grep or baseline by IDA TypeScript-first sibling, rivet-ts — same pipeline, contracts authored as TS types, Hono runtime
Documentation
Getting Started · Contracts · CLI Reference · Type Mapping · Runtime Validation (the precise scope of what is and isn't enforced at runtime)
License
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
This package has no dependencies.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.46.0 | 95 | 10/3/2026 |
| 0.45.1 | 97 | 9/30/2026 |
| 0.45.0 | 96 | 9/29/2026 |
| 0.44.1 | 109 | 9/21/2026 |
| 0.44.0 | 100 | 9/19/2026 |
| 0.43.0 | 111 | 9/18/2026 |
| 0.42.0 | 107 | 9/17/2026 |
| 0.41.0 | 221 | 7/13/2026 |
| 0.40.0 | 144 | 7/11/2026 |
| 0.39.0 | 127 | 7/11/2026 |
| 0.38.0 | 125 | 6/13/2026 |
| 0.37.0 | 145 | 6/12/2026 |
| 0.36.1 | 138 | 6/12/2026 |
| 0.36.0 | 131 | 6/12/2026 |
| 0.35.0 | 130 | 6/12/2026 |
| 0.34.3 | 137 | 5/22/2026 |
| 0.34.0 | 132 | 4/21/2026 |
| 0.33.2 | 141 | 4/16/2026 |
| 0.33.1 | 145 | 4/16/2026 |
| 0.33.0 | 132 | 4/15/2026 |