Skip to main content

mts1b_foundation.openapi — full reference

Helpers to export OpenAPI 3.1 / JSON-Schema from any foundation model. Plug into FastAPI, openapi-typescript, Postman, or any spec-driven tooling.

to_openapi(*models, info=None) -> dict

Build a minimal OpenAPI 3.1 document with the given models as components.schemas.

from mts1b_foundation.openapi import to_openapi
from mts1b_foundation.orders import Order, Fill
from mts1b_foundation.positions import Position

spec = to_openapi(
Order, Fill, Position,
info={"title": "OMS API", "version": "0.1.0"},
)

print(spec["openapi"]) # "3.1.0"
print(list(spec["components"]["schemas"].keys())) # ["Order", "Fill", "Position", ...]

Pydantic v2 stores nested model defs in $defs; to_openapi flattens them into top-level components.schemas.

Common patterns

Generate the full ecosystem spec

from mts1b_foundation.openapi import to_openapi
from mts1b_foundation.orders import Order, Fill
from mts1b_foundation.positions import Position, TaxLot
from mts1b_foundation.market_data import Quote, Bar, Trade, MarketSnapshot
from mts1b_foundation.signals import Signal, TargetWeight
from mts1b_foundation.risk import RiskEnvelope, OrderRejection, HaltRequest
from mts1b_foundation.funds import FundConfig, NavSnapshot, TransferRequest


spec = to_openapi(
Order, Fill, Position, TaxLot,
Quote, Bar, Trade, MarketSnapshot,
Signal, TargetWeight,
RiskEnvelope, OrderRejection, HaltRequest,
FundConfig, NavSnapshot, TransferRequest,
info={"title": "MTS1B API", "version": "0.1.0",
"description": "Open-source quant stack — full type contract"},
)
import json
with open("openapi.json", "w") as f:
json.dump(spec, f, indent=2)

Plug into FastAPI

from fastapi import FastAPI
from mts1b_foundation.orders import Order
from mts1b_foundation.openapi import to_openapi


app = FastAPI()


@app.post("/v1/orders", response_model=Order)
async def submit_order(order: Order) -> Order:
"""Submit an order to the OMS."""
return await oms.submit(order)


# FastAPI auto-generates /openapi.json; foundation types are referenced as $ref

Generate TypeScript types

# After exporting spec to openapi.json:
npx openapi-typescript openapi.json -o api-types.ts
// api-types.ts (auto-generated)
export interface paths {
"/v1/orders": {
post: {
requestBody: { content: { "application/json": components["schemas"]["Order"] } };
responses: { 200: { content: { "application/json": components["schemas"]["Order"] } } };
};
};
}

export interface components {
schemas: {
Order: {
order_id: string;
idempotency_key: string;
symbol: string; // Symbol → string in OpenAPI
side: "buy" | "sell" | "short" | "cover";
quantity: string; // Decimal → string
// ...
};
Fill: { /* ... */ };
Position: { /* ... */ };
};
}

The webui (mts1b-frontends) uses this exact pipeline.

Generate clients in other languages

OpenAPI spec works for any language with a client generator:

# Go
oapi-codegen -package mts1b -generate types openapi.json > types.go

# Rust
openapi-generator generate -i openapi.json -g rust -o rust-client

# Java
openapi-generator generate -i openapi.json -g java -o java-client

Community plugins use this to write non-Python clients (Rust OMS, Go market-data scrapers, etc).

Decimal handling

Pydantic represents Decimal as JSON-Schema type: string with format: "decimal". This preserves precision across language boundaries.

schema = Order.model_json_schema()
print(schema["properties"]["quantity"])
# {"type": "string", "format": "decimal", "exclusiveMinimum": "0"}

Consumers in other languages should deserialize as BigDecimal / decimal.Decimal, NOT float.

Symbol handling

Symbol is a str subclass with __get_pydantic_core_schema__. In OpenAPI it appears as plain type: string:

schema = Order.model_json_schema()
print(schema["properties"]["symbol"])
# {"type": "string", "minLength": 1}

For typed clients, you can wrap with a brand:

// TypeScript brand pattern
type Symbol = string & { __brand: "Symbol" };

Datetime handling

Pydantic v2 serializes datetime as ISO-8601 strings:

import datetime as dt
order = Order(..., created_at=dt.datetime(2026, 5, 23, 16, 0, tzinfo=dt.timezone.utc))
print(order.model_dump_json())
# {"created_at":"2026-05-23T16:00:00Z", ...}

In OpenAPI: {"type": "string", "format": "date-time"}.

Schema versioning

Each foundation release ships a __version__. When you publish an OpenAPI spec, embed it:

import mts1b_foundation

spec = to_openapi(
Order, Fill,
info={
"title": "MTS1B API",
"version": mts1b_foundation.__version__,
"x-foundation-version": mts1b_foundation.__version__,
},
)

Consumers can check x-foundation-version to detect breaking changes between deploys.

Limitations + sharp edges

dict fields aren't typed

Signal.metadata is dict with no internal structure. OpenAPI represents it as additionalProperties: true. To get typed metadata, subclass:

class IcMetadata(BaseModel):
ic: float
t_stat: float
backtest_sharpe: float

class TypedSignal(Signal):
metadata: IcMetadata

Then to_openapi(TypedSignal, ...) exposes the structure.

Union[A, B] becomes oneOf (sometimes lossy)

Some clients struggle with oneOf discrimination. Use a discriminator field if you have a union:

from typing import Annotated, Literal, Union
from pydantic import Field

class OrderEvent(BaseModel):
event_type: Literal["created", "filled", "canceled"]
# ...

Cyclic refs

Pydantic v2 handles them; some downstream generators don't. If you have cyclic types (rare in foundation), test with your target generator first.

See also