Generate a Swift SDK from your OpenAPI spec

Octri reads your OpenAPI document and writes a Swift package with async/await methods and Codable models, pushes it to your repository with a semver tag for Swift Package Manager, and rebuilds it every time the spec changes.

Install
dependencies: [
    .package(url: "https://github.com/acme/acme-swift", from: "1.0.0")
]

The client they actually get

What your users write once the Swift 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.

Every operation is async throws with labeled arguments, and optional ones default to nil, so a call from SwiftUI’s .task modifier is a single line.

Swift Package Manager resolves straight from a repository, so there is no registry account to connect. Octri pushes the generated package and a semver tag, and Xcode picks it up under File, Add Package Dependencies.

Client object (default)
import AcmeApi

let acme = Acme(config: ClientConfig(
    baseUrl: "https://api.acme.com/v1",
    auth: ClientAuthConfig(bearer: token)
))

// listPaginated returns an AsyncThrowingStream. Pages load as you iterate.
for try await user in acme.users.listPaginated(limit: 100) {
    print(user.id)
}

let invoice = try await acme.invoices.create(customerId: "cus_123", amountCents: 4900)
Free functions
import AcmeApi

configureClient(ClientConfig(
    baseUrl: "https://api.acme.com/v1",
    auth: ClientAuthConfig(bearer: token)
))

for try await user in listUsersPaginated(limit: 100) {
    print(user.id)
}

The package at a glance

What a developer checks before adding the Swift package to a project.

Runs on
iOS 15, macOS 12, tvOS 15, watchOS 8; Swift 5.9 tools
Transport
URLSession, with FoundationNetworking on Linux
Runtime dependencies
None
Models
Codable structs
Pagination
AsyncThrowingStream, consumed with for try await
Ships to
Your repository, as a semver tag for SwiftPM

When a call fails

SdkHttpError carries a kind enum for the statuses apps branch on most, so a catch ... where clause or a switch handles them without comparing integers. A declared error model is decoded onto error.data.

Typed errors
do {
    let invoice = try await acme.invoices.create(customerId: "cus_123", amountCents: 4900)
} catch let error as SdkHttpError where error.kind == .rateLimit {
    // 429 after the built-in retries. Offer a retry in the UI.
} catch let error as SdkHttpError {
    logger.error("acme failed: \(error.statusCode) request \(error.requestId)")
}

What you decide for Swift

Swift has no engine setting: URLSession is the transport an Apple-platform consumer expects, and it brings no third-party package into their app.

  • Client style

    class-namespaced (default), class, namespaced, functions

    A client class with a lazily built property per resource. Functions are module-level and read a global configuration, which suits a small app with one API.

  • Argument style

    positional (default), object

    Labeled arguments, the Swift call-site convention, with optional ones defaulting to nil. Object collects them into a request struct.

  • Doc comments

    full (default), minimal

    Full writes Swift doc comments that Xcode shows in Quick Help and DocC can build into a reference.

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.

The repository URL is the dependency

There is no registry in between: your users’ Package.swift and Xcode projects name your repository directly. Rename or move it and every consumer’s package resolution breaks, including apps that pinned it. SwiftPM also reads only semver tags, so a pushed commit without a tag is invisible to from: "1.0.0". Pick the repository name once.

Swift Package Manager dependency
github.com/acme/acme-swift
  • Not possible: Move or rename the repository
  • Not possible: Resolve an untagged commit
  • Still possible: Release with a semver tag

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.
  1. 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.

  2. 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.

  3. Shape the client

    Client style, argument style, namespacing, auth, retries and pagination, set once for the project and overridden per language or per endpoint.

  4. Generate and read it

    The package is generated, compiled and handed to you as source you can read before anyone installs it.

  5. 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.

Questions

No. The package is distributed for Swift Package Manager, which Xcode has built in, by git tag from your repository.