sideload
Self-hosted ad-hoc iOS distribution — one-tap installs of signed .ipa builds onto a registered iPhone, no cable and no laptop in reach. Upload a build and sideload content-addresses it, extracts its metadata from Info.plist and the embedded provisioning profile, generates the OTA manifest iOS expects, and serves a one-tap install page.
How it works
iOS fetches the manifest, the .ipa, and the prompt icons from its own install daemon — not Safari — so those requests carry no cookie. The install payload therefore lives under high-entropy capability tokens at /i/{token}/…, reachable without a session, while the index and uploads stay session-gated. The same token mechanism backs public single-use share links at /s/{token} that self-revoke after one install.
Rich app pages
Each app has an optional page: a tagline, a markdown description, ordered screenshots, and a per-version changelog ("what's new"). It is entirely additive — an app with no page content renders its plain version list, and clearing the content restores that. The same description and screenshots also dress up the public share page, so a shared build reads like a product page rather than a bare install button. Screenshots are content-addressed and served immutably at /shots/{id}.
Device whitelist
Ad-hoc builds only install on devices whose UDID is in the signing profile. Each app keeps a per-app whitelist of the UDIDs to provision in the next build. It diffs against the latest build's profile, so every device shows as in build (already provisioned) or pending (register, rebuild, re-upload), and exports a tab-separated UDID Name list for the Developer portal's bulk registration. The owner device — SIDELOAD_OWNER_UDID — is pinned into every app's list automatically.
Public self-enrolment is opt-in per app: a token at /register/{token} serves a Profile Service .mobileconfig that, once installed on an iPhone, posts that device's UDID straight into the whitelist. No more emailing UDIDs around.
Authentication
The index, build pages, and management UI are gated by the shared password. Uploads and the management API use the write key — send X-API-Key or Authorization: Bearer. The install routes (/i/{token}/…) and the public share landing (/s/{token}) authenticate by their unguessable token, not a header. /status stays public.
API
.ipa (raw body or multipart field ipa). Returns { id, cid, bundleId, version, buildNumber, profileExpiry, deviceCount, installURL }. Write key.{ "builds": [Build, …] }. Write key.?ttl=30m|2h|24h&max=1|3|unlimited. Returns { token, shareURL, expiresAt }. Write key.itms-services:// install link points here. Token-gated..mobileconfig that posts the device's UDID into the app's whitelist. Token-gated.{ "service": "sideload", "ok": true, "builds": N }Record Shape
Build
{
"id": "bafk… (16-char content address)",
"cid": "bafk…",
"bundleId": "com.example.app",
"appName": "Example",
"version": "1.4.2",
"buildNumber": "231",
"team": "Team Name",
"profileExpiry": "2027-04-03T00:00:00Z",
"deviceCount": 4,
"sizeBytes": 12345678,
"createdAt": "2026-06-17T00:00:00Z"
}
Builds are grouped by bundle id — each upload of the same app becomes a version of it. Distribution is ad-hoc: a build installs only on a device whose UDID is registered in the signing profile, and stays installable until the profile expires (about a year).