Phone SDK reference
Every Helix Phone SDK namespace and method — runtime, account, permissions, storage, notifications, media, camera, wallet, payments, contacts, messages, calls, presence, social, feeds, ui, and lifecycle.
This is the complete surface of the Helix Phone SDK as it ships today (v0.1.0). Each method lists
its required permission scope; calls without a granted scope return
null/[] rather than throwing. Methods marked mock are wired against a mocked
backend until the real integration lands — see Proposed & in-progress.
Creating the SDK
import { createHelixPhoneSdk } from "@helix/phone-sdk";
const phone = createHelixPhoneSdk({
appId: "studio.example", // required — your manifest appId
accessToken?: string, // first-party/server only; omit for sandboxed apps
manifest?: PhoneAppManifest,
bridgeNonce?: string, // usually read from ?helixBridgeNonce automatically
});createHelixPhoneSdk returns an object with the namespaces below. It auto-selects the
bridge or direct transport.
runtime
| Method | Returns | Notes |
|---|---|---|
runtime.bootstrap() | PhoneBootstrapItem | null | Account, installed apps, permissions, launch context, runtime config. |
runtime.manifest() | PhoneAppManifest | null | This app's manifest. |
runtime.launchContext() | PhoneAppLaunchContext | null | How the app was opened (web / notification / shell). |
account
| Method | Returns | Scope |
|---|---|---|
account.getCurrentUser() | PhoneAccount | null | account.basic (always granted) |
const me = await phone.account.getCurrentUser();
// { userId, username, displayName, avatarUrl, phoneNumber, bio }permissions
| Method | Returns | Notes |
|---|---|---|
permissions.list() | PhonePermissionScope[] | Scopes currently granted to this app. |
permissions.has(scope) | boolean | Check a single scope. |
permissions.request(scopes) | PhonePermissionScope[] | Request one scope or an array; returns granted scopes. Throws if a scope wasn't declared in the manifest. |
storage
Per-app key–value storage. Keys match ^[a-zA-Z0-9._:-]{1,128}$; values are JSON up to 32 KB.
Writes are atomic upserts. Scope: storage.app.
| Method | Returns |
|---|---|
storage.get<T>(key) | T | null |
storage.set<T>(key, value) | PhoneAppStorageRecord<T> | null |
storage.delete(key) | void |
await phone.storage.set("draft", { caption: "gm", assetId });
const draft = await phone.storage.get<{ caption: string; assetId: string }>("draft");notifications
Create and manage this app's notifications. Push is rate-limited to ~12/hour per app.
Scope: notifications.push.
| Method | Returns | Notes |
|---|---|---|
notifications.list() | PhoneNotification[] | This app's notifications. |
notifications.push({ title, body, deepLink? }) | PhoneNotification | null | title ≤120, body ≤500. deepLink opens the app at a path. |
notifications.markRead(notificationId) | PhoneNotification | null | |
notifications.markAppRead(appId?) | void | Marks all of this app's notifications read. |
await phone.notifications.push({
title: "Render ready",
body: "Your postcard finished exporting.",
deepLink: "studio.example/exports",
});media
The player's album (photos/videos). Upload accepts a File. Scope: media.pick.
| Method | Returns | Notes |
|---|---|---|
media.listAlbum() | AlbumAsset[] | All album assets, newest first. |
media.pickFromAlbum() | AlbumAsset | null | The most recent asset (the picker UI returns the chosen one). |
media.upload(file, { caption?, worldName? }) | AlbumAsset | null | Upload an image/video into the album. |
const photo = await phone.media.pickFromAlbum();
if (photo) attach(photo);camera
Capture into the album. mock — mockCapture currently produces a placeholder asset
(or copies sourceAssetId); real world/device capture is proposed.
Scope: camera.capture.
| Method | Returns |
|---|---|
camera.mockCapture({ sourceAssetId?, caption?, worldName?, type? }) | AlbumAsset | null |
wallet
Read-only balances. Scope: wallet.read.
| Method | Returns |
|---|---|
wallet.getBalance() | PhoneWalletBalance | null |
const w = await phone.wallet.getBalance();
// w.lix.totalBalance, w.coins.balance — read-onlypayments
In-app purchases priced in LIX, from the app's manifest iap catalog. Purchases are idempotent.
mock — entitlements are granted but LIX isn't debited yet (see
In-app purchases). Scope: payments.
| Method | Returns | Notes |
|---|---|---|
payments.getProducts() | PhoneIapProduct[] | The app's product catalog. |
payments.purchase(sku, { idempotencyKey? }) | PhoneIapEntitlement | null | Pass an idempotencyKey so retries can't double-charge. |
payments.getEntitlements() | PhoneIapEntitlement[] | What the player owns. |
payments.restore() | PhoneIapEntitlement[] | Re-fetch entitlements (e.g. on a new device). |
const ent = await phone.payments.purchase("demo_theme_pack", {
idempotencyKey: crypto.randomUUID(),
});contacts
The player's contacts, backed by the Helix social graph. Scope: social.connections.read.
| Method | Returns |
|---|---|
contacts.list() | PhoneAccount[] |
messages
Threads and sending, consent-gated. Scope: messages.send_with_consent.
| Method | Returns | Notes |
|---|---|---|
messages.threads() | MessageThread[] | Threads with unread counts. |
messages.send({ participantId, body, attachment? }) | Message | null | body ≤2000; attachment is an AlbumAsset. |
messages.markRead(threadId) | MessageThread | null |
calls
Voice calls and call logs. mock — calls are simulated until the voice API lands.
Scope: voice.calls.
| Method | Returns |
|---|---|
calls.logs() | CallLog[] |
calls.mockCall(contactId) | CallLog | null |
calls.mockIncoming(contactId) | CallLog | null |
calls.updateStatus(callId, status, durationSeconds?) | CallLog | null |
presence
The player's current world/instance. mock — returns a fixed world until live
presence lands. Scope: presence.world.
| Method | Returns |
|---|---|
presence.getCurrentWorld() | PhonePresenceWorld | null |
social
Opt-in discovery profiles and swiping (the dating/meeting primitive). Swipe matching is
mock. Scope: social.discovery.
| Method | Returns | Notes |
|---|---|---|
social.getMyDiscoveryProfile() | PhoneSocialDiscoveryProfile | null | |
social.updateDiscoveryProfile({ displayName?, bio?, avatarUrl?, active? }) | PhoneSocialDiscoveryProfile | null | Create/update. |
social.disableDiscoveryProfile() | void | Sets the profile inactive. |
social.getDiscoveryFeed({ limit? }) | PhoneSocialDiscoveryProfile[] | Opted-in candidates. |
social.recordSwipe(candidateUserId, "like" | "pass") | PhoneSocialDiscoverySwipeResult | null |
feeds: helixgram & h
Because Helixgram (photo/video) and H (short posts) are first-party, the SDK exposes them directly.
Both require social.discovery; Helixgram createPost/createStory also require media.pick.
phone.helixgram
| Method | Returns |
|---|---|
helixgram.feed(page?) / helixgram.followingFeed(page?) | HelixgramPost[] |
helixgram.userPosts(userId, page?) | HelixgramPost[] |
helixgram.stories(page?) / helixgram.userStories(userId, page?) | HelixgramStory[] |
helixgram.userSocial(userId) | HelixgramProfile | null |
helixgram.searchUsers(query, limit?) | HelixgramProfile[] |
helixgram.follow(userId) / helixgram.unfollow(userId) | result |
helixgram.createPost({ asset, caption, location }) | HelixgramPost | null |
helixgram.createStory({ asset, caption, location }) | HelixgramStory | null |
helixgram.like(postId) / helixgram.unlike(postId) | result |
helixgram.comment(postId, body) / helixgram.deleteComment(postId, commentId) | result |
helixgram.deletePost(postId) / helixgram.deleteStory(storyId) | void |
helixgram.report({ subjectType, subjectId, reason, details? }) | result |
page is { limit?, before? } for cursor pagination.
phone.h
| Method | Returns |
|---|---|
h.feed(page?) / h.followingFeed(page?) | HPost[] |
h.trends({ limit? }) | HTrend[] |
h.userPosts(userId, page?) | HPost[] |
h.replies(postId, page?) | HPost[] |
h.userSocial(userId) | HProfile | null |
h.createPost(body) / h.reply(postId, body) | HPost | null |
h.repost(postId) / h.like(postId) / h.unlike(postId) | result |
h.follow(userId) / h.unfollow(userId) | result |
h.report({ subjectType, subjectId, reason, details? }) | result |
ui
Native phone UI affordances. No scope required (UI-only).
| Method | Returns | Notes |
|---|---|---|
ui.toast(body) | void | Transient toast. |
ui.confirm({ title, body?, confirmLabel?, cancelLabel? }) | boolean | Native confirm dialog. |
ui.shareSheet({ items }) | PhoneUiShareResult | OS share sheet; items are { title?, text?, url? }. |
ui.setBadge(count) | void | Set this app's home-screen badge count. |
lifecycle
| Method | Returns | Notes |
|---|---|---|
lifecycle.on(event, handler) | () => void | Subscribe; returns an unsubscribe fn. Events: foreground / suspend / resume / close. |
lifecycle.emit(event, snapshot?) | void | Mostly internal. |
See Runtime & the bridge for the lifecycle model.
Comprehensive & kept in sync
This page covers the entire shipping SDK surface. A
drift-detection pipeline compares it against lib/phone/sdk.ts
on the helix3 branch and flags any namespace or method that lands or changes, so the reference can't
silently fall behind the code.
Permissions & consent
The 12 Phone permission scopes, how apps declare them, how players grant them, and the audit trail behind every grant.
The App Store
How third-party phone apps are registered, installed, and managed — the manifest, install lifecycle, and where the publishing flow is today versus where it's going.