Sign in with Starchild

Add Starchild OAuth login to a web app and verify every protected request on your server.

Before you start

You need a Starchild account, a browser client, a backend, and the exact origin where the client runs. Create an OAuth app from iamstarchild.com → More → OAuth Apps → Create App. The direct OAuth Apps page may also resolve directly.

Enter:

  • Name, required.
  • Allowed Origin(s), required. Register the third-party page origin only, including scheme and port, with no path, query, hash, or trailing slash. Register every local origin you use, such as http://localhost:5173 and http://localhost:3333. Do not register http://localhost:6066; that is the Starchild web app's own local port and is already whitelisted server-side.
  • Scopes, selecting only what the app needs.
  • System Prompt, optional. It customizes the Agent's behaviour for chat apps.

Scopes and approval

Scope Access Approval
profile Name, avatar, user ID, and guest status Automatic
chat Agent conversation, threads, containers, skills, media, jobs, wallet reads, and WebSockets Admin review
credit:read Credit balance and history Admin review
credit:write Top-ups, gift cards, and Points exchange; includes credit:read Admin review

Selecting only profile gives the app a Client ID immediately. Any other scope enters admin review, and the Client ID is issued after approval. Origins from approved and active apps are added to the API CORS allowlist roughly every five minutes. Wait for that refresh after changing an origin.

Install the SDK
npm install starchild-auth-sdk
# or: yarn add starchild-auth-sdk
# or: pnpm add starchild-auth-sdk

For plain HTML, load the UMD build. It exposes window.StarchildAuth, the constructor itself:

<script src="https://unpkg.com/starchild-auth-sdk/dist/starchild-auth.umd.cjs"></script>

If unpkg is unavailable, use the npmmirror UMD build or self-host the file. The ESM build, starchild-auth.js, requires a bundler or <script type="module">.

Initialize login

The default sign-in scope is profile. Add chat, credit:read, or credit:write only after the OAuth app is approved for those scopes.

<button id="login-btn">Sign in with Starchild</button>
<button id="logout-btn" hidden>Log out</button>
<p id="status"></p>

<script>
const loginButton = document.getElementById('login-btn')
const logoutButton = document.getElementById('logout-btn')
const status = document.getElementById('status')

const auth = new StarchildAuth({
  clientId: 'YOUR-CLIENT-ID',
  scope: 'profile', // add approved scopes only when needed
  onLogin: ({ accessToken, refreshToken, expiresIn, userInfo }) => {
    // userInfo = { userInfoId, agentName, agentAvatar, isGuest }
    status.textContent = `Signed in as ${userInfo.agentName}`
    loginButton.hidden = true
    logoutButton.hidden = false
  },
  onLogout: () => {
    status.textContent = ''
    loginButton.hidden = false
    logoutButton.hidden = true
  },
  onTokenRefreshFailed: () => {
    status.textContent = 'Your session expired. Sign in again.'
    loginButton.hidden = false
    logoutButton.hidden = true
  },
})

loginButton.addEventListener('click', async () => {
  try {
    await auth.login()
  } catch (error) {
    if (error?.message?.includes('cancelled')) {
      status.textContent = 'Sign-in was cancelled.'
    } else if (error?.message?.includes('blocked')) {
      status.textContent = 'The popup was blocked. Allow popups and try again.'
    } else {
      status.textContent = 'Login did not finish. Try again.'
    }
  }
})

logoutButton.addEventListener('click', () => auth.logout())
</script>

Call login() directly from a click or another user gesture. onLogin receives { accessToken, refreshToken, expiresIn, userInfo }. The SDK also calls it when it restores an existing session on page load.

Guest accounts and account binding

There is no loginAsGuest(). Guests and permanent accounts use the same auth.login() flow. A user can choose continue-as-guest in the Starchild popup, which returns userInfo.isGuest === true.

Binding a permanent login method must happen on Starchild. Do not build a guest-binding page in your app:

const user = await auth.fetchUserInfo()
if (auth.isGuest() || user?.isGuest) {
  const newTab = auth.bindAccount()
  if (!newTab) window.location.href = auth.getBindAccountUrl()
}

After the user binds Google, X, Email, Phone, or Wallet on Starchild, the next fetchUserInfo() or token refresh should show isGuest: false. Decide explicitly whether guests may perform payments or other irreversible actions. If they may not, require binding first.

Call your backend

Read the current access token when each request is made:

const response = await fetch('/api/me', {
  headers: { Authorization: `Bearer ${auth.getToken()}` },
  cache: 'no-store',
})

Do not cache the access token in application state for later reuse. The SDK refreshes short-lived access tokens automatically.

Verify every token on your server

Treat browser identity data as untrusted. Extract the bearer token and verify it with Starchild:

GET https://ai-api.iamstarchild.com/v1/oauth/userinfo
Authorization: Bearer <access-token>

A valid token returns the user's identity. Reject missing, invalid, expired, or unverifiable tokens with 401 Unauthorized. Token refresh and logout use https://go-api.iamstarchild.com/v1/oauth/{refresh,logout}.

import json
import urllib.error
import urllib.request

USERINFO_URL = "https://ai-api.iamstarchild.com/v1/oauth/userinfo"

def verify_starchild_token(token):
    if not token:
        return None
    request = urllib.request.Request(
        USERINFO_URL,
        headers={"Authorization": "Bearer " + token},
    )
    try:
        with urllib.request.urlopen(request, timeout=8) as response:
            payload = json.loads(response.read().decode("utf-8"))
    except (urllib.error.HTTPError, urllib.error.URLError, TimeoutError, ValueError):
        return None
    data = payload.get("data", payload) if isinstance(payload, dict) else {}
    user_id = data.get("userInfoId") or data.get("user_info_id") or data.get("id")
    if not user_id:
        return None
    return {
        "user_id": str(user_id),
        "display_name": data.get("agentName") or data.get("agent_name") or "",
        "avatar": data.get("agentAvatar") or data.get("agent_avatar") or "",
        "is_guest": bool(data.get("isGuest") or data.get("is_guest")),
    }

Use userInfoId as the stable key for ownership, quotas, and application records. A 403 with { "detail": "Insufficient scope: …" } means the token lacks a requested scope. That differs from a 401 invalid or expired token. Surface the distinction and let the user re-authorize with the required scope.

For payments, one-time actions, and permission checks, fail closed when verification is unavailable.

Session behavior
  • Access tokens last 15 minutes and refresh automatically every 12 minutes and on visibilitychange.
  • Refresh tokens last 7 days and are stored in localStorage under starchild_rt_{clientId}.
  • autoLogin: true restores a saved session on page load.
  • auth.logout() clears the local session and revokes it through POST /v1/oauth/logout on go-api.
  • Prefer refreshToken() and automatic refresh. getRefreshToken() exposes the in-memory copy of the value already stored locally. Treat it like a password: never log it, send it to third parties, or put it in a URL.

Keep a strict Content Security Policy, avoid unsafe third-party inline scripts, sanitize rendered content, and treat cross-site scripting as an account-security issue.

Next steps

Once the OAuth app is approved for the relevant scopes, the same SDK can access chat, threads, containers, skills, media, scheduled jobs, wallet reads, WebSockets, and credits. The Starchild Auth SDK API reference covers those namespaces, error handling, endpoints, and local testing.

Troubleshooting
Problem What to check
Popup blocked Call login() directly from a click handler.
Origin mismatch Match the third-party page origin exactly, including scheme and port. Never use localhost:6066 for the third-party app.
CORS fails on a new origin Check allowed origins, approval and active status, then allow roughly five minutes for the CORS refresh.
onLogin does not fire The user may have closed or rejected the popup. Check the login error message for cancelled or blocked.
403 Insufficient scope: … Re-authorize with the required approved scope.
userInfo.isGuest === true Use auth.bindAccount() to open Starchild Linked accounts. Do not build your own binding flow.
401 after refresh The 7-day refresh token expired. Ask the user to sign in again.
Node or script cannot call login() Popup login requires a real browser and a user gesture. Log in in the browser, then use the access token for script requests.
Launch checklist
  • [ ] Request only the scopes you need.
  • [ ] Register production and every local origin, never localhost:6066.
  • [ ] Handle userInfo.isGuest and offer auth.bindAccount().
  • [ ] Catch scope errors and distinguish them from expired tokens.
  • [ ] Keep refresh-token output out of logs, URLs, and third-party backends.
  • [ ] Verify every protected request server-side and fail closed.
  • [ ] Key application data by userInfoId.

For constructor options and the full method and namespace reference, continue to the Starchild Auth SDK API reference.

Current SDK version

These examples are written against starchild-auth-sdk 0.4.1 and the starchild-auth integration guide 1.11.0.

User information
type UserInfo = {
  userInfoId: string
  agentName: string
  agentAvatar: string
  isGuest: boolean
}
Constructor options
Option Default Purpose
clientId Required OAuth Client ID
scope profile Space-separated scopes
origin https://iamstarchild.com Login and account-binding site
apiBase https://go-api.iamstarchild.com/v1 Token endpoints
chatApiBase https://ai-api.iamstarchild.com Userinfo and Agent REST APIs
clawdApiBase https://preview.iamstarchild.com Chat stream, jobs, and model endpoints
clawdWsBase wss://preview.iamstarchild.com Sync, terminal, and metrics WebSockets
creditApiBase https://credit.iamstarchild.com Credits API
autoLogin true Restore a saved session
refreshInterval 720000 Refresh interval in milliseconds
onLogin / onLogout / onTokenRefresh / onTokenRefreshFailed None Session lifecycle callbacks

Defaults point at production. Override them only when running the full Starchild backend locally.

Methods

login() · logout() · isLoggedIn() · getToken() · getRefreshToken() · getUserInfo() · fetchUserInfo() · isGuest() · getBindAccountUrl() · bindAccount() · refreshToken() · destroy()

For the 60+ chat, thread, container, skills, media, jobs, wallet, and credits methods, use the dedicated API reference.

API errors

JSON helpers throw the exported StarchildAuthError on non-2xx responses. It includes status, code, detail, path, insufficientScope, and response. sendMessage() and reconnectStream() return a raw Response, so check .ok yourself.

import { StarchildAuthError } from 'starchild-auth-sdk'

try {
  await auth.credit.getBalance()
} catch (error) {
  if (error instanceof StarchildAuthError && error.insufficientScope) {
    await auth.login()
  }
}
API reference

For all other methods, grouped aliases, endpoint paths, SSE events, fly-force-instance-id, server-side tokens, and the browser-versus-Node local-testing matrix, see the Starchild Auth SDK API reference.

Keep this page focused

This page is the sign-in path. The linked API reference carries the full Agent surface so a developer can get a working login first without searching through every chat and credits method.