RoadieKit is a small Swift SDK for chat, embeddings, and AI image generation and editing. It authenticates each device anonymously — no login screen and no provider key in the app — calling Roadie’s native /v1 surface for chat and embeddings and the OpenAI-compatible surface at /openai/v1 for images. Roadie holds your provider keys (BYOK), meters usage per device, and enforces free-vs-pro limits. RoadieKit requires iOS 18+.

Install

Add RoadieKit with Swift Package Manager. In Xcode, choose File → Add Package Dependencies… and enter https://github.com/paroaria/roadiekit.git and pick the Up to Next Major rule from 0.2.0, or add it to Package.swift:

Configure the client

A Roadie client needs a RoadieConfig (where the gateway lives, and which image model to request) and a token provider (how each request is authenticated). Swap the provider between local development and production without changing any call site:
  • StaticTokenProvider — a fixed bearer read from the run scheme’s ROADIE_STATIC_TOKEN environment variable (an rd_sk_… key or a pre-minted client token). The working simulator / dev path.
  • AppAttestTokenProvider — silently mints a per-device client token via Apple App Attest, using the publishable key. The production path; it needs a real device (App Attest is unavailable in the simulator).

RoadieConfig

Keep configuration out of source: read the base URL and publishable key from Info.plist and inject ROADIE_STATIC_TOKEN from the run scheme so it is never committed or shipped in a Release build.

Method surface

The Roadie client is an actor exposing three surfaces — chat, embeddings, and images — each a nonisolated, Sendable value type you call directly (no actor hop). Every surface shares one authorization path (the TokenProvider below), one idempotency key per logical call, a single re-mint on a 401, and a transient-failure retry.

Chat

Send a message

ChatRequest takes a model (an alias like "smart" or an explicit "provider/model" id), the messages array, and optional temperature, maxOutputTokens, and user (an EndUser(id:plan:) for per-end-user attribution and limits). Build messages with the .system / .user / .assistant / .tool(_:toolCallId:) helpers. The SDK sets stream for you. ChatResponse surfaces text (the concatenated reply — a convenience for message.text), the resolved model / provider, finishReason, token usage, estimated cost, the gateway requestId, and any message.toolCalls the model requested.

Stream a reply

chat.stream(_:) returns an AsyncThrowingStream of typed events; concatenate the contentDeltas to render the reply as it arrives:
The connection opens lazily when iteration begins and ends on the terminal messageEnd (or a terminal error event). A mid-stream failure is surfaced as a .error(RoadieError) — the last event, nothing follows it — while transport and pre-stream HTTP failures are thrown. Cancelling the surrounding task tears down the underlying connection.

Embeddings

input accepts a single String or a [String]. vectors returns the embeddings in input order (providers may return them out of order; RoadieKit restores it by index). dimensions is optional for models that support truncation, and user attributes usage to an end user.

Images

Both image methods return the generated image’s raw bytes (PNG unless the server’s output_format is changed) — the caller turns them into a UIImage. size is an ImageSize (.auto, .square, .landscape, .portrait), background is an ImageBackground (.transparent, .opaque, .auto), and quality is an ImageQuality (.low, .medium, .high, .auto). n defaults to 1. On edit, mask is optional; when present, only the masked region is regenerated. edit defaults background to .transparent; generate defaults it to .opaque.

Edit an image

Generate from a prompt

ImageSize.forAspectRatio(_:) maps a width ÷ height ratio to the closest concrete size (> 1.2 → landscape, < 0.83 → portrait, otherwise square).

Authentication providers

RoadieKit resolves the Authorization: Bearer … value through a TokenProvider, shared by every surface:
  • StaticTokenProvider(token:) returns a fixed token; invalidateToken() is a no-op, so the built-in single remint-retry cannot loop.
  • AppAttestTokenProvider(config:) mints short-lived client tokens by attesting the device with Apple App Attest and exchanging the proof at the gateway’s /v1/device/* endpoints (authorized with the publishable key). Only the App Attest key id is persisted (Keychain); the token lives in memory and is reminted before expiry (or after a 401). It requires a publishableKey in the config and a real device.
On a DeviceCheck-capable device, AppAttestTokenProvider also attaches an optional per-device DeviceCheck token to the mint so the gateway’s free-tier guard can resist reinstall farming. This is wired internally — there is no separate public DeviceCheck provider to construct — and it fails open (an unsupported device or a token-generation error simply omits it). See Mobile device auth.

Error handling

Every HTTP or decoding failure is thrown as a RoadieError; task cancellation is Swift’s own CancellationError (never a RoadieError). The same RoadieError.kind model covers chat, embeddings, and images:
RoadieError also exposes statusCode, apiType, apiCode, message, requestId, and isRetryable, and conforms to LocalizedError (errorDescription). RoadieKit handles two cases for you: it re-mints once on a 401 (an expired client token) and retries transient network and 5xx failures. A client .timedOut is deliberately not retried — generation can run for tens of seconds, so a timeout usually means the server is still working, and a retry would double-execute an expensive call. An .insufficientQuota error drops the cached token and is re-thrown unchanged (no retry) — your cue to show the paywall; see Free → Pro with StoreKit.

Not included

  • Request-side tool definitions — the chat surface parses tool-call responses (message.toolCalls and the streamed toolCallDelta), but declaring tools / tool_choice / response_format on the request is not yet modeled. For tool-calling and structured-output flows, drive them from a server-side SDK (@roadie/sdk or roadie for Python).
  • A separate DeviceCheck provider — DeviceCheck is an internal detail of AppAttestTokenProvider, not a public API you construct.

Next steps

iOS quickstart

Add RoadieKit to an app end to end.

Send a message to OpenAI

Set up the SDK, send a chat message through Roadie, and get a reply.

Mobile device auth

How App Attest mints per-device client tokens with no account.

Free → Pro with StoreKit

Convert the free tier to a paid subscription and flip the device’s plan.

Android SDK — Roadie

The Kotlin SDK for Android, with chat, embeddings, images, and Play Integrity device auth.