chatgpt_auth_kit (Flutter)
Sign in with a ChatGPT subscription and call the Codex Responses API from your Flutter app. Pure Dart, no native bridge required.
Zero third-party SDK dependencies. Auth, secure-storage credentials, auto-refresh, and minimal Responses / Models / Usage clients. To plug into another OpenAI client (dart_openai, raw package:http, etc.), see Plugging in other OpenAI SDKs.
Install
dependencies:
chatgpt_auth_kit: ^0.1.0Transitively pulls flutter_secure_storage, url_launcher, crypto, http.
iOS setup
In ios/Runner/Info.plist:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsLocalNetworking</key>
<true/>
</dict>
<key>NSLocalNetworkUsageDescription</key>
<string>Used to receive the OAuth callback on 127.0.0.1:1455.</string>Usage
import 'package:chatgpt_auth_kit/chatgpt_auth_kit.dart';
final flow = OAuthFlow();
final store = CredentialsStore();
Future<Credentials> login() async {
final creds = await flow.run(); // launches system browser by default
await store.save(creds);
return creds;
}If you want to present an in-app browser yourself (recommended on iOS):
final creds = await flow.run(
present: (url) async {
// Show a SafariViewController-style page using webview_flutter_plus or
// flutter_inappwebview, passing `url`.
},
onComplete: () async {
// Close the in-app browser.
},
);Auto-refresh
For long-lived sessions, wrap credentials in a RefreshingCredentialsProvider once at app start. It hands out fresh credentials on demand, persists each refresh to secure storage, and coalesces concurrent refreshes:
final provider = RefreshingCredentialsProvider(creds, store: store);
final chat = ResponsesClient(provider: provider);
final models = ModelsClient(provider: provider);
final usage = UsageClient(provider: provider);Every .fetch() / .stream(...) call goes through the provider, so a near-expiry token is refreshed before the request goes out — you never have to call OAuthClient.refresh(...) yourself. For one-off use, each client also has a .fromCredentials(...) constructor that takes a single Credentials value.
Streaming a completion
final client = ResponsesClient(provider: provider);
await for (final event in client.stream([
Message(role: Role.system, content: 'You are helpful.'),
Message(role: Role.user, content: "What's a fun weekend in Berlin?"),
])) {
if (event is DeltaEvent) stdout.write(event.text);
}Discover available models
final models = await ModelsClient(provider: provider).fetch();
for (final m in models) print('${m.slug} ${m.displayName ?? ""} default=${m.isDefault}');Read plan & quota
final usage = await UsageClient(provider: provider).fetch();
print('plan=${usage.planType} primary=${usage.primary?.usedPercent}');Plugging in other OpenAI SDKs
The built-in ResponsesClient is intentionally minimal (text streaming + usage). If you'd rather use a different HTTP layer or a future dart_openai Responses-API binding, point it at the Codex backend with the right headers and body shape. ChatGPTAuthKit gives you the credentials; the integration is config in your app.
Where it lives
- Host:
chatgpt.com - Base path:
/backend-api/codex - Bearer token:
credentials.accessToken
Headers Codex requires
Authorization: Bearer <accessToken>ChatGPT-Account-ID: <accountID>OpenAI-Beta: responses=experimentaloriginator: codex_cli_rs(or your app's identifier)
Body shape Codex enforces — a 400 if any of these is wrong
instructions: Stringreasoning: { effort, summary }include: ["reasoning.encrypted_content"]store: falseinputas a structured array — system messages collapse intoinstructions, user/assistant become{type: "message", role, content: [{type: "input_text"|"output_text", text}]}
Example: package:http
Drop these helpers anywhere in your app:
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:chatgpt_auth_kit/chatgpt_auth_kit.dart';
const codexBaseUrl = 'https://chatgpt.com/backend-api/codex';
Map<String, String> codexHeaders(Credentials credentials, {String originator = 'codex_cli_rs'}) {
return {
'Authorization': 'Bearer ${credentials.accessToken}',
'OpenAI-Beta': 'responses=experimental',
'originator': originator,
if (credentials.accountID.isNotEmpty) 'ChatGPT-Account-ID': credentials.accountID,
};
}
Map<String, dynamic> codexQueryBody({
required String prompt,
String model = 'gpt-5.5',
String instructions = "Follow the user's instructions.",
String reasoningEffort = 'medium',
bool stream = true,
}) {
return {
'model': model,
'input': [
{
'type': 'message',
'role': 'user',
'content': [{'type': 'input_text', 'text': prompt}],
}
],
'instructions': instructions,
'reasoning': {'effort': reasoningEffort, 'summary': 'auto'},
'include': ['reasoning.encrypted_content'],
'store': false,
'stream': stream,
};
}Then send a request:
final creds = await provider.currentCredentials();
final res = await http.post(
Uri.parse('$codexBaseUrl/responses'),
headers: {...codexHeaders(creds), 'Content-Type': 'application/json'},
body: jsonEncode(codexQueryBody(prompt: 'Say hi.')),
);Tools, function calling, and multi-turn conversation state are surface area of whichever client library you pick — Codex's body-shape and header rules above apply equally to those flows.
License
MIT.