fastapi-openrpc
Declarative JSON-RPC 2.0 interface and OpenRPC 1.0 service discovery for FastAPI applications.
Features
- Declarative Registration —
@method()decorator with automatic JSON Schema inference - OpenRPC Service Discovery —
GET /openrpc.jsonreturns complete OpenRPC document - Pydantic Support — Automatic schema generation with
$refreferences - RpcContext Propagation — Secure caller context injection via FastAPI
Depends() - HMAC Authentication — Built-in signature verification with Nonce replay protection
- Role-Based Authorization — Method-level role declarations with composition semantics
- Business Errors — Structured error codes with
errorsarray in OpenRPC document
Installation
pip install fastapi-openrpc
Optional dependencies:
# Redis support (Nonce replay protection)
pip install fastapi-openrpc[redis]
# Development dependencies
pip install fastapi-openrpc[dev]
Quick Start
from fastapi import FastAPI
from fastapi_openrpc import OpenRpcRouter, RpcContext, method
app = FastAPI()
openrpc_router = OpenRpcRouter(
title="My API",
version="1.0.0",
description="My JSON-RPC service",
)
@method(name="getUser")
async def get_user(ctx: RpcContext, user_id: str) -> dict:
"""Get user information by user_id."""
return {"id": user_id, "name": "Alice", "roles": ctx.roles}
@method(name="listUsers")
async def list_users(ctx: RpcContext, limit: int = 10) -> list[dict]:
"""Return a list of users."""
return [{"id": i, "name": f"User {i}"} for i in range(limit)]
app.include_router(openrpc_router)
After starting the server, access GET /openrpc.json to view the OpenRPC service document.
Authentication
HMAC Authentication
from fastapi_openrpc.authentication import HMACAuthentication
auth = HMACAuthentication(secret_key="your-secret-key")
openrpc_router = OpenRpcRouter(
authentication=auth,
)
Nonce Replay Protection
auth = HMACAuthentication(
secret_key="your-secret-key",
enable_nonce=True,
redis_url="redis://localhost:6379",
nonce_ttl=3600,
connect_timeout=2.0,
operation_timeout=5.0,
cleanup_timeout=2.0,
)
Header format:
{
"roles": ["admin"],
"nonce": "550e8400-e29b-41d4-a716-446655440000"
}
Authorization
from fastapi_openrpc import Require
@method(name="adminAction", required_roles=["admin"])
async def admin_action(ctx: RpcContext) -> dict:
"""Admin only."""
return {"status": "done"}
@method(name="editContent", required_roles=["editor", "admin"], require=Require.ALL)
async def edit_content(ctx: RpcContext) -> dict:
"""Requires both editor and admin roles."""
return {"status": "edited"}
Error Handling
Built-in Errors
from fastapi_openrpc import InvalidParams, MethodNotFound
@method(name="divide")
async def divide(ctx: RpcContext, a: float, b: float) -> float:
if b == 0:
raise InvalidParams("Division by zero")
return a / b
Built-in error codes:
| Error | Code |
|---|---|
| Parse error | -32700 |
| Invalid Request | -32600 |
| Method Not Found | -32601 |
| Invalid Params | -32602 |
| Internal Error | -32603 |
| Forbidden | -32000 |
| Replay Detected | -32002 |
Business Errors
Define custom errors by subclassing BusinessError:
from fastapi_openrpc import BusinessError
# Define error codes starting from -32100
class UserNotFoundError(BusinessError):
code = -32101
message = "User not found"
def __init__(self, user_id: str):
super().__init__(data={"user_id": user_id})
Register error classes with methods using the errors parameter:
from examples.errors import UserNotFoundError # Your error class
@method(name="getUser", errors=[UserNotFoundError])
async def get_user(ctx: RpcContext, user_id: str) -> dict:
user = db.get(user_id)
if not user:
raise UserNotFoundError(user_id=user_id)
return user
The errors array appears in the OpenRPC document for that method, enabling SDK auto-generation of typed exceptions:
{
"name": "getUser",
"errors": [
{"code": -32101, "message": "User not found"}
]
}
Library reserves -32000 ~ -32099, application custom errors start from -32100.
API Reference
OpenRpcRouter
from concurrent.futures import ThreadPoolExecutor
executor = ThreadPoolExecutor(max_workers=4)
openrpc_router = OpenRpcRouter(
title="My API",
version="1.0.0",
description="...",
servers=[{"url": "https://api.example.com"}],
authentication=HMACAuthentication(secret_key="..."), # Optional
executor=executor, # Optional, for sync handler thread pool
)
app.include_router(openrpc_router)
@method() Decorator
@method(
name="myMethod", # RPC method name
tags=["admin"], # OpenRPC tags
summary="Short description",
deprecated=False,
required_roles=["admin"], # Required roles
require=Require.ALL, # Role composition semantics
errors=[MyError], # Business error subclasses this method may raise
)
async def my_handler(ctx: RpcContext, arg1: str, arg2: int = 10) -> dict:
return {"result": arg1}
Note:
ctx: RpcContextis a reserved parameter name for injecting caller context.
Usage Examples
# Call RPC method
curl -X POST http://localhost:8000/ \
-H "Content-Type: application/json" \
-d '{"jsonrpc": "2.0", "id": 1, "method": "getUser", "params": {"user_id": "42"}}'
# Service discovery
curl http://localhost:8000/openrpc.json
Documentation
For detailed user documentation, see docs/index.md.
Additional guides:
- Tutorial: Pydantic Models — 4 scenarios covering flat params, nested models, optional fields, and validation
- How-to: Error Handling — custom errors, Nonce replay protection, best practices
- Explanation: Concurrency Model — ThreadPoolExecutor, sync handler wrapping, closure safety
Contributing
Issues and Pull Requests are welcome.
License
MIT
Metadata
Release files for fastapi-openrpc 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| fastapi_openrpc-0.2.0.tar.gz | 30.2 kB | Without provenance | Details for fastapi_openrpc-0.2.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| fastapi_openrpc-0.2.0-py3-none-any.whl | Python 3 | none | any | Details for fastapi_openrpc-0.2.0-py3-none-any.whl |
Total release size: 66.0 kB
Release files / fastapi_openrpc-0.2.0.tar.gz
| Download URL | fastapi_openrpc-0.2.0.tar.gz |
|---|---|
| Size | 30.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e4c7dccae0d614b86c5d8372f56edbb6dc48d859d3e3730cd3418e886fc02920
|
|
BLAKE2b-256 checksum How to use checksums |
f15e9d7f015913712a6dc01a18021f0b2f664e1e0146c6d1cd375d24b5ed1755
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.6 {"installer":{"name":"uv","version":"0.11.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / fastapi_openrpc-0.2.0-py3-none-any.whl
| Download URL | fastapi_openrpc-0.2.0-py3-none-any.whl |
|---|---|
| Size | 35.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f0149b4c49ddfd0bc944b2a5fc2d6638309461e558c1612eceddb1e56d417efd
|
|
BLAKE2b-256 checksum How to use checksums |
d676cdea473e6cd986740ab0a0725ab6302864a32c6dfe59c172a288b66d8d10
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.6 {"installer":{"name":"uv","version":"0.11.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Security for fastapi-openrpc 0.2.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.