Skip to main content
Chappie handles authentication by guiding users through a device-code sign-in flow tied to their existing ChatGPT account. Once sign-in completes, the SDK stores credentials securely in the iOS or macOS Keychain — your app never receives raw tokens. Instead, you observe a published ChappieAuthState value and an optional ChappieAccountInfo struct that contains the user’s email, plan, and account identifiers. Chappie also refreshes expired tokens automatically before every request, so you don’t need to manage token lifetimes yourself.

How authentication works

1

User taps sign in

Your app calls startDeviceCodeSignIn() — or uses the ChappieSignIn button that calls it for you. The SDK requests a short-lived device code from OpenAI.
2

User approves in a browser

The user opens the verification URL (automatically or manually), signs in with their ChatGPT account, and pastes the device code. Chappie polls in the background until the code is approved.
3

Credentials stored in Keychain

When polling succeeds, the SDK writes the access and refresh tokens to Keychain using the accessibility and account settings you configured. Your app never sees the raw tokens.
4

Auth state published to your UI

ChappieAuthSession.state transitions to .signedIn and accountInfo is populated. Subsequent app launches restore the session from Keychain automatically.
5

Automatic token refresh

Before every request, Chappie checks whether the stored access token is expired. If it is, the SDK silently refreshes it using the stored refresh token and writes the new credential back to Keychain.

Creating an auth session

Create a single ChappieAuthSession as a @StateObject in the view that owns your sign-in flow. Chappie reads the Keychain at init time, so the session is already populated if the user signed in during a previous launch.
@StateObject private var authSession = Chappie.authSession()
Pass a ChappieConfiguration to customise which Keychain slot is used, or to support multiple accounts. See Multi-Account for details.

Observing auth state

ChappieAuthSession is an ObservableObject with four published properties:
PropertyTypeDescription
stateChappieAuthStateThe current sign-in lifecycle stage
deviceLinkStateChappieDeviceLinkStateFine-grained progress through the device-code exchange
accountInfoChappieAccountInfo?Email, plan, userID, and accountID — populated once signed in
lastErrorChappieAuthError?The most recent auth failure, or nil if none
React to state in your SwiftUI views to switch between signed-out and signed-in UI:
switch authSession.state {
case .signedIn:
    print(authSession.accountInfo?.plan?.displayName ?? "Unknown plan")
case .signedOut:
    // Show sign-in UI
case .reauthenticationRequired:
    // Prompt the user to sign in again
case .failed(let message):
    print("Auth failed:", message)
default:
    break
}

ChappieAuthState values

CaseMeaning
.signedOutNo credential is stored; the user has not signed in
.signingInSign-in has started; the device code is being requested
.awaitingDeviceCode(userCode:verificationURL:)The device code is ready; waiting for the user to approve it in a browser
.signedIn(expiresAt:)The user is signed in; expiresAt is the credential expiry date, or nil if unknown
.reauthenticationRequiredThe stored refresh token is invalid or missing; the user must sign in again
.failed(String)An unrecoverable error occurred; the associated string is a human-readable message

Account info

After a successful sign-in, authSession.accountInfo is a ChappieAccountInfo value with the following fields:
FieldTypeDescription
emailString?The user’s ChatGPT email address
userIDString?The ChatGPT user identifier
accountIDString?The ChatGPT account (organisation) identifier
planChappiePlan?The user’s current plan, with a human-readable displayName
isFedRAMPAccountBooltrue for FedRAMP-compliant accounts
if let info = authSession.accountInfo {
    print(info.email ?? "No email")
    print(info.plan?.displayName ?? "Unknown plan")
}

Signing out

Call signOut() to clear the stored Keychain credential and reset state to .signedOut. Any in-progress sign-in polling task is cancelled automatically.
authSession.signOut()

Manual token refresh

Chappie refreshes credentials automatically before requests. If you need to force a refresh — for example, after the user upgrades their plan — call refreshAuth():
try await authSession.refreshAuth()
If the refresh token is invalid or missing, refreshAuth() transitions the session to .reauthenticationRequired and throws a ChappieAuthError. Prompt the user to sign in again in that case.

Error reference

CaseWhen it occurs
.expiredDeviceCodeThe user did not approve the device code before it expired
.cancelledSignInSign-in was cancelled programmatically or by the user
.networkFailure(String)A network request failed; the associated string is the underlying error message
.pollingTimeoutThe SDK stopped polling before the device code was approved
.callbackTimeoutThe sign-in callback did not arrive in time
.authorizationDenied(String)OpenAI denied the authorisation request
.tokenExchangeFailed(statusCode:body:)The token exchange returned an HTTP error; inspect statusCode and body
.missingRefreshTokenNo refresh token is stored; the user must sign in from scratch
.secureRandomFailed(statusCode:)Secure random number generation failed at the OS level
.invalidResponseThe sign-in response could not be parsed
.unknown(String)An unexpected error occurred; the associated string contains details
When requiresReauthentication is true on a ChappieAuthError (currently .missingRefreshToken and certain token-exchange failures), the SDK automatically signs out and transitions to .reauthenticationRequired. All other errors leave existing credentials intact so a retry is possible.

Your app never receives raw access or refresh tokens through Chappie’s public API. ChappieAccountInfo exposes only identity and plan metadata derived from the stored credential.