Rivet.Attributes 0.46.0

dotnet add package Rivet.Attributes --version 0.46.0
                    
NuGet\Install-Package Rivet.Attributes -Version 0.46.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Rivet.Attributes" Version="0.46.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Rivet.Attributes" Version="0.46.0" />
                    
Directory.Packages.props
<PackageReference Include="Rivet.Attributes" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Rivet.Attributes --version 0.46.0
                    
#r "nuget: Rivet.Attributes, 0.46.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Rivet.Attributes@0.46.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Rivet.Attributes&version=0.46.0
                    
Install as a Cake Addin
#tool nuget:?package=Rivet.Attributes&version=0.46.0
                    
Install as a Cake Tool

<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.Attributes targets net8.0, net9.0 and net10.0).
  • .NET 9 or later for the dotnet-rivet tool: 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

Documentation

Getting Started · Contracts · CLI Reference · Type Mapping · Runtime Validation (the precise scope of what is and isn't enforced at runtime)

License

MIT

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  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 is compatible.  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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net10.0

    • No dependencies.
  • net8.0

    • No dependencies.
  • net9.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.46.0 250 10/3/2026
0.45.1 86 9/30/2026
0.45.0 115 9/29/2026
0.44.1 100 9/21/2026
0.44.0 95 9/19/2026
0.43.0 106 9/18/2026
0.42.0 95 9/17/2026
0.41.0 521 7/13/2026
0.40.0 140 7/11/2026
0.39.0 125 7/11/2026
0.38.0 123 6/13/2026
0.37.0 130 6/12/2026
0.36.1 126 6/12/2026
0.36.0 118 6/12/2026
0.35.0 117 6/12/2026
0.34.3 140 5/22/2026
0.34.2 122 5/11/2026
0.34.1 113 5/11/2026
0.34.0 116 4/21/2026
0.33.2 126 4/16/2026
Loading failed