What is Smithy?
Smithy is an interface definition language for describing services and generating clients, servers and documentation from that description. It comes from Amazon Web Services, where the IDL it is based on has been used internally for over a decade, and AWS uses it to model its services and generate SDKs. The language, the build tooling and several code generators are open source under the smithy-lang organization.
The design decision that separates it from OpenAPI is that Smithy is protocol agnostic. A Smithy model describes the shapes of data and the operations of a service; how those operations appear on the wire is a separate choice, expressed by applying a protocol trait. The same model can be served as a REST API with JSON bodies, as an RPC API with CBOR bodies, or both. OpenAPI starts from the HTTP request and works inward; Smithy starts from the service and treats HTTP as one binding of it.
If you are building a single HTTP API whose consumers want an OpenAPI document, write OpenAPI. Reach for Smithy when you generate SDKs in several languages and care about their quality, when one service is exposed over more than one protocol, or when you want resource lifecycle rules checked at build time rather than in review.
1. The IDL
A model file declares its IDL version, a namespace, and then shapes. The version control statement for the current IDL is $version: "2"; a minor version such as "2.1" can be stated when a model depends on a feature from that release.
$version: "2"namespace example.weather
service Weather { version: "2006-03-01" resources: [City]}
resource City { identifiers: { cityId: CityId } read: GetCity list: ListCities}
@readonlyoperation GetCity { input := { @required cityId: CityId } output := { @required name: String } errors: [NoSuchResource]}
@pattern("^[A-Za-z0-9 ]+$")string CityIdEvery shape has a shape ID such as example.weather#City. Traits are the @-prefixed annotations, and they are where almost all of the meaning lives: constraints, documentation, protocol bindings, pagination and more.
2. Shapes
| Kind | Shapes |
|---|---|
| Simple | blob, boolean, document, string, byte, short, integer, long, float, double, bigInteger, bigDecimal, timestamp |
| Aggregate | list, map, structure, union |
| Service | service, operation, resource |
timestamp is a point in time with no wire format of its own; the protocol, or a @timestampFormat trait, decides whether it serializes as an RFC 3339 string, epoch seconds or an HTTP date. document is an untyped value for the rare field that genuinely has no schema; using it is opting out of everything Smithy is for, so keep it out of the core of a model.
Mixins (@mixin on a shape, with [...] where it is used) share members between structures, and the apply statement attaches traits to a shape defined elsewhere, which is how you add documentation or protocol bindings to a model you do not own.
3. Services, resources and operations
An operation has an input structure, an output structure (both default to smithy.api#Unit) and a list of errors, each of which must carry the @error trait set to client or server.
A resource groups operations around an entity with identifiers, and assigns them lifecycle roles. Smithy validates those roles, which is the feature OpenAPI has no equivalent for:
| Lifecycle | Meaning | Rule |
|---|---|---|
create |
Create with a server-generated identifier | Collection operation |
put |
Create or replace with a client-supplied identifier | Must be @idempotent, not @readonly |
read |
Retrieve current state | Must be @readonly |
update |
Modify | Must not be @readonly |
delete |
Remove | Must be @idempotent, not @readonly |
list |
List instances | Collection operation, must be @readonly |
A put that is not idempotent, or a read with side effects, fails the build rather than reaching production. Model resources even if you only ever ship one protocol: the validation is worth it on its own.
4. Protocols
A protocol is a trait on the service. The ones in common use:
| Trait | Shape on the wire |
|---|---|
aws.protocols#restJson1 |
REST: HTTP binding traits place members in the path, query, headers or JSON body |
aws.protocols#restXml |
As restJson1, with XML bodies |
aws.protocols#awsJson1_0, awsJson1_1 |
RPC: every operation is a POST with a JSON body |
aws.protocols#awsQuery, ec2Query |
Legacy form-encoded requests with XML responses |
smithy.protocols#rpcv2Cbor |
RPC with CBOR bodies, not tied to AWS |
smithy.protocols#rpcv2Json |
The same RPC shape with JSON bodies |
For a REST protocol, every operation needs the @http trait and its members are bound with @httpLabel, @httpQuery, @httpHeader and @httpPayload:
@readonly@http(method: "GET", uri: "/cities/{cityId}")operation GetCity { input := { @required @httpLabel cityId: CityId }}rpcv2Cbor ignores HTTP binding traits entirely. Every request is a POST to {prefix?}/service/{serviceName}/operation/{operationName}, carries Smithy-Protocol: rpc-v2-cbor, and uses application/cbor for bodies:
POST /service/Weather/operation/GetCity HTTP/1.1Host: api.example.comSmithy-Protocol: rpc-v2-cborContent-Type: application/cborAccept: application/cborSuccessful responses are always 200. Errors are 400, or 500 for an error marked server, unless the error shape sets @httpError. Under restJson1, the error type travels in the X-Amzn-Errortype header.
For a new service-to-service API with generated clients on both ends, rpcv2Cbor is the better default: smaller payloads, no URI design to argue over, and no ambiguity about where a member goes. For a public API that people will call with curl, use restJson1 and design the URIs properly.
5. Code generation and OpenAPI
smithy build, driven by a smithy-build.json file, validates the model and runs projections and plugins over it. Code generators exist for Java, TypeScript, Rust, Python, Kotlin, Go and Scala, at varying levels of maturity; check the generator’s own status before committing a production SDK to it. See SDK generation for what to expect from generated clients in general.
The smithy-openapi plugin converts a model to OpenAPI 3.0.2 by default, or 3.1.0. Conversion only works for HTTP protocols with RESTful routing, and it is lossy: resource metadata, custom traits (without an extension), endpoint and host-label traits and prefix headers do not survive. Treat the Smithy model as the source of truth and the OpenAPI document as a generated artifact, never the reverse.
Best Practices
- Model resources and their lifecycles, not just operations. It is the part of Smithy that catches design mistakes.
- Keep one model per service and apply protocols to it, rather than forking models per protocol.
- Do not hand-edit OpenAPI generated from Smithy. The next build overwrites it, and the edit was a change to the contract that never went through the model.
- Version and lint the model like code. Smithy’s validators and the design-first governance practices that apply to OpenAPI apply here too.