Generate a Dart and Flutter SDK from your OpenAPI spec
Octri reads your OpenAPI document and writes a Dart package with typed models for Flutter apps and Dart servers, publishes it to pub.dev under your own package name, and rebuilds it every time the spec changes.
flutter pub add acme_apiThe client they actually get
What your users write once the Dart package is installed.
Two call-site shapes come out of the same spec. class-namespaced is the default: a client object whose operations are grouped by resource, which is the surface every widely used SDK ships and the one your own reference docs already assume. functions is the override: no client object, one configure call, and each operation called directly. It is a single setting, per language, and nothing else about the client changes with it.
Parameters are named, and every call returns a Future or a Stream, so it drops into a FutureBuilder or a StreamBuilder in a Flutter widget without an adapter.
Published to pub.dev under your own publisher. The same package installs with dart pub add in a server or CLI and flutter pub add in an app.
import 'package:acme_api/acme_api.dart';
final acme = Acme(ClientConfig(
baseUrl: 'https://api.acme.com/v1',
auth: ClientAuthConfig(bearer: token),
));
// listPaginated is a lazy Stream. await for asks for the next page on demand.
await for (final user in acme.users.listPaginated(limit: 100)) {
print(user.id);
}
final invoice = await acme.invoices.create(customerId: 'cus_123', amountCents: 4900);import 'package:acme_api/acme_api.dart';
SdkConfig.configure(ClientConfig(
baseUrl: 'https://api.acme.com/v1',
auth: ClientAuthConfig(bearer: token),
));
await for (final user in listUsersPaginated(limit: 100)) {
print(user.id);
}The package at a glance
What a developer checks before adding the Dart package to a project.
- Runs on
- Dart 3; Flutter on iOS, Android, macOS, Windows and Linux
- Transport
- dart:io HttpClient
- Runtime dependencies
- None
- Models
- Immutable classes with fromJson, no build_runner step
- Pagination
- Lazy Stream, consumed with await for
- Ships to
- pub.dev, for dart pub and flutter pub
When a call fails
Each common status is its own subclass, so on clauses read like the cases a screen has to handle. A declared error model is decoded onto e.data for an is check.
try {
await acme.invoices.create(customerId: 'cus_123', amountCents: 4900);
} on SdkRateLimitError {
// 429 after the built-in retries. Show a retry button.
} on SdkHttpError catch (e) {
print('acme failed: ${e.statusCode} request ${e.requestId}');
}What you decide for Dart
Dart has no engine or data-model setting. Models are plain Dart, so your users never run code generation of their own.
Client style
class-namespaced (default), class, namespaced, functionsA client object with a field per resource, easy to hand to a Provider or a Riverpod provider. Functions are top-level and read a global SdkConfig.
Argument style
positional (default), objectNamed parameters, required where your spec says required, which is the Dart idiom. Object collects them into a request class.
Doc comments
full (default), minimalFull writes dartdoc, which feeds the documentation share of your pub points score. Keep it on full.
Method naming and namespacing work the same in every language: operations are grouped by your spec’s tags or by path, and the namespace word is dropped from the method name unless you ask to keep the operationId whole.
pub.dev names are lowercase, underscored and permanent
Package names on pub.dev are global, use underscores (hyphens are not allowed) and stay yours for good. A version can be retracted within seven days, which discourages new resolution but does not free the number. Choose the name and check the first version on a device before you publish.
acme_api- Not possible: Put a hyphen in the name
- Not possible: Reuse a version number
- Still possible: Retract a version within seven days
The same in every generated client
The parts nobody wants to hand-write, and the parts a hand-written client usually skips.
- Retries with exponential backoff and jitter, on the statuses that mean "try again", with a per-attempt timeout.
- Idempotency keys on the methods that need them, so a retried write does not become two.
- Auth wired in from your spec’s security scheme, read from wherever your users keep secrets.
- Typed errors split by cause: a failed constraint, an HTTP status, a network failure and a timeout are four different things.
- Pagination that follows whichever contract your spec declares, cursor, offset, page number or next URL, without prefetching the collection.
- Server-sent events as a native stream, for the operations that stream.
- Error reporting to Octri’s monitoring, pre-wired and switched off until a consumer opts in.
Point it at your spec
Upload an OpenAPI or Swagger document, paste a URL, or connect the repository it lives in so a merge to main updates it.
Read the audit
The spec is scored out of 10 on what a generator can do with it, with every missing schema and colliding operationId named. 9 of the fourteen rules carry a button that writes the fix.
Shape the client
Client style, argument style, namespacing, auth, retries and pagination, set once for the project and overridden per language or per endpoint.
Generate and read it
The package is generated, compiled and handed to you as source you can read before anyone installs it.
Publish, then keep publishing
Release to the registry under your own account. Every later spec change regenerates the client, and the version number comes from your own document.
Or in another language
One spec, ten languages. The other nine are generated from the same document, with the same settings, at the same time.
Questions
Not today. The transport is dart:io’s HttpClient, which Flutter supports on iOS, Android, macOS, Windows and Linux, and on Dart servers and CLIs, but which browsers do not provide.