Shipeasy

Swift

The Shipeasy Swift server SDK — async/await, SwiftPM, local evaluation, configs, experiments, and tracking.

Production readyOn this page · 4 min readUpdated · June 18, 2026Works with · Swift · SwiftPM · iOS 15+ · macOS 12+

Server-side flags, configs, experiments, and tracking for Swift, async/await throughout. Evaluates locally against a background-polled blob — see the shared evaluation model.

Server key only

This SDK is server-key only — never embed it in an iOS app bundle. A future ShipeasyClient package will cover client-key, mobile use.

Install

dependencies: [
    .package(url: "https://github.com/shipeasy-ai/sdk-swift.git", from: "0.1.0"),
]

Configure

Call configure() once at startup with the server key, plus an optional attributes transform that maps your user object onto the Shipeasy attribute map.

import Shipeasy

try await configure(
    apiKey: ProcessInfo.processInfo.environment["SHIPEASY_SERVER_KEY"]!,
    attributes: { u in ["user_id": u.id, "plan": u.plan] }
)

Bind a client to the user

Construct a Client from your user object — the attributes transform turns it into the evaluation context, so the getters take no user argument. The methods are async because the underlying Engine is an actor.

let flags = try Client(currentUser)

Evaluate a feature flag

let enabled = await flags.getFlag("new_checkout")

An optional default is returned only when the gate can't be evaluated (client not ready or gate absent) — a gate that evaluates false returns false:

let on = await flags.getFlag("new_checkout", default: true)

For the value plus the reason, use getFlagDetail — see Evaluation reasons:

let d = await flags.getFlagDetail("new_checkout")
// d.value  -> Bool
// d.reason -> one of the FlagReason raw values

Read a dynamic config

let cfg = await flags.getConfig("billing_copy", default: ["headline": "Welcome"])

Resolve an experiment

let r = await flags.getExperiment("checkout_button", defaultParams: ["color": "blue"])
if r.inExperiment {
    r.group   // "treatment"
    r.params  // ["color": ...]
}

Track an event

await flags.track(eventName: "purchase", properties: ["amount": 49])

Anonymous visitors

The SDK is framework-agnostic, so it ships AnonId primitives rather than a middleware. Resolve the shared __se_anon_id cookie off the request and echo it back so logged-out traffic buckets like the browser:

let resolved = AnonId.resolve(cookieHeader: req.headers["cookie"].first)
let on = await Engine.shared.getFlag("new_checkout", user: ["anonymous_id": resolved.id])
if resolved.minted {
    res.headers.add(name: "set-cookie", value: AnonId.setCookieHeader(resolved.id, secure: true))
}

Testing

Engine.forTesting() does zero network — no key, initialize/track are no-ops. Seed with override* and reset with clearOverrides. See Testing.

let engine = Engine.forTesting()
await engine.overrideFlag("new_checkout", true)
let enabled = await engine.getFlag("new_checkout", user: ["user_id": "u_123"]) // true
await engine.clearOverrides()

Errors & feedback

Report a handled exception with see() so it folds into the errors primitive with a one-sentence consequence — what feature broke and how it degraded. see() rides the same configure() boot (server key); there is no separate error SDK or second key.

do {
    try await chargeCard(order, prices)
} catch {
    see(error).causesThe("checkout").to("use cached prices").extras(["order_id": order.id])
}

Use seeViolation("large query") for a non-exception problem. Full grammar — consequence phrasing, control-flow exceptions, anti-patterns — is in Error reporting with see().

The in-app bug & feature report overlay is a standalone <script> tag you drop into your frontend — platform-agnostic, no server SDK required. See The devtools overlay.

Was this page helpful?
✎ Edit this page

On this page