
Xai Grok Onboarding
- 2.2k installs
- 18 repo stars
- Updated July 27, 2026
- starchild-ai-agent/official-skills
xai-grok-onboarding is an agent skill that |.
About
Use any active xAI account X Premium X Premium SuperGrok or SuperGrok Heavy for grok 4 3 grok build 0 1 grok 4 20 and multi agent models No separate API key needed This is standard OAuth 2 0 RFC 8628 Device Authorization Grant not a vendor custom flow The JWT issued by auth x ai carries a tier claim higher tiers unlock more models from v1 models Observed mapping xAI does not publish this officially Tier Subscription Approx model access 1 X Premium 8 mo grok 4 3 baseline 2 X Premium 16 mo grok 4 20 0309 variants 3 SuperGrok 30 mo reasoning models 4 SuperGrok Heavy 300 mo grok build 0 1 multi agent status reports the user s tier so they know which models will be available
- Connect an xAI account (X Premium / X Premium+ / SuperGrok / SuperGrok Heavy) via OAuth 2.0 device-code login.
- Use when the user wants to sign in with their xAI account (e.g. "use my SuperGrok", "log in with Grok", "connect my X Pr
- tags: [xai, grok, oauth, supergrok, x-premium, login, subscription, multi-agent]
- Follow xai-grok-onboarding SKILL.md steps and documented constraints.
- Follow xai-grok-onboarding SKILL.md steps and documented constraints.
Xai Grok Onboarding by the numbers
- 2,173 all-time installs (skills.sh)
- +59 installs in the week ending Jul 28, 2026 (Skillselion tracking)
- Ranked #472 of 16,659 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Jul 28, 2026 (Skillselion catalog sync)
xai-grok-onboarding capabilities & compatibility
- Capabilities
- connect an xai account (x premium / x premium+ / · use when the user wants to sign in with their xa · tags: [xai, grok, oauth, supergrok, x premium, l · follow xai grok onboarding skill.md steps and do
- Use cases
- orchestration
What xai-grok-onboarding says it does
Connect an xAI account (X Premium / X Premium+ / SuperGrok / SuperGrok Heavy) via OAuth 2.0 device-code login.
Use when the user wants to sign in with their xAI account (e.g. "use my SuperGrok", "log in with Grok", "connect my X Premium").
tags: [xai, grok, oauth, supergrok, x-premium, login, subscription, multi-agent]
npx skills add https://github.com/starchild-ai-agent/official-skills --skill xai-grok-onboardingAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 2.2k |
|---|---|
| repo stars | ★ 18 |
| Last updated | July 27, 2026 |
| Repository | starchild-ai-agent/official-skills ↗ |
When should an agent use xai-grok-onboarding and what problem does it solve?
|
Who is it for?
Developers invoking xai-grok-onboarding as documented in the skill source.
Skip if: Skip when requirements fall outside xai-grok-onboarding documented scope.
When should I use this skill?
|
What you get
Outputs aligned with the xai-grok-onboarding SKILL.md workflow and stated deliverables.
- OAuth-authenticated xAI session
- Grok model access in coding agents
By the numbers
- Skill version 1.3.0
- Supports 4 subscription tiers: X Premium, X Premium+, SuperGrok, SuperGrok Heavy
Files
🟢 xAI OAuth Onboarding
Use any active xAI account — X Premium, X Premium+, SuperGrok, or SuperGrok Heavy — for grok-4.3, grok-build-0.1, grok-4.20-* and multi-agent models. No separate API key needed.
This is standard OAuth 2.0 (RFC 8628 Device Authorization Grant), not a vendor-custom flow.
Tier → model access
The JWT issued by auth.x.ai carries a tier claim; higher tiers unlock more models from /v1/models. Observed mapping (xAI does not publish this officially):
| Tier | Subscription | Approx. model access |
|---|---|---|
| 1 | X Premium ($8/mo) | grok-4.3 baseline |
| 2 | X Premium+ ($16/mo) | + grok-4.20-0309 variants |
| 3 | SuperGrok ($30/mo) | + reasoning models |
| 4 | SuperGrok Heavy ($300/mo) | + grok-build-0.1 + multi-agent |
status() reports the user's tier so they know which models will be available.
This is a script-mode skill — no tools registered. Read this file, then call the exports from a bash block.
See also
byok-custom-modelskill — for vendor-key BYOK setup (xAI API key from console.x.ai, different mechanism — bills per-token, NOT subscription-backed)chatgpt-codex-onboardingskill — same pattern, for ChatGPT/Codex subscriptionconfig/context/references/model-onboarding.md— overall model-selection landscape
Backend compatibility
This skill depends on the core.xai_grok Python package inside the platform image (shipped 2026-05-22). On older images the skill loads cleanly but every public function returns:
{
"ok": false,
"error": "xai_oauth_backend_unavailable",
"detail": "ModuleNotFoundError: core.xai_grok not found in any of: [/app, /data/workspace/starchild-clawd]",
"hint": "Update the platform image OR fall back to byok-custom-model with an xAI API key."
}If you see this response, do NOT retry — the platform itself needs an update. Either wait for the image refresh or guide the user to byok-custom-model with an API key from console.x.ai.
---
When to use this skill
✅ Use when the user EXPLICITLY says one of:
- "Sign in with my Grok / SuperGrok account"
- "Use my SuperGrok / X Premium subscription"
- "Connect SuperGrok Heavy"
- "Login with xAI / Grok"
- "Use my Grok Heavy subscription"
❌ Do NOT use for:
- "Add Grok via API key" / "I have an xAI API key" → use
byok-custom-model(the xAI template) - Other vendors (Anthropic, OpenAI, Gemini, Qwen, etc.) → use
byok-custom-model - "Add the Grok model" without subscription mention → ASK the user which path they want (subscription OAuth vs. API key BYOK)
The two paths are mutually exclusive billing-wise. Subscription OAuth uses the user's monthly quota; BYOK API key uses console.x.ai pay-per-token credits.
---
Critical preflight — account gate awareness
xAI has a known backend gate that denies OAuth grants for some accounts even with an active SuperGrok subscription. This is upstream xAI behavior, not a client bug. Symptoms:
- Verification page loads, but clicking "Approve" returns
access_deniedfrom the token endpoint - Hermes Agent has documented the same in issue #26847
If poll() returns AccountAccessDenied: 1. Verify the user's SuperGrok subscription is active (grok.com / settings) 2. Suggest they try the verification URL in their already-logged-in browser (not a fresh incognito) 3. If still denied → fall back to BYOK API key path (byok-custom-model skill, xAI template, key from https://console.x.ai)
Do NOT silently retry — the gate is deterministic per account, retrying wastes time.
---
Flow
The flow has 4 user-visible steps. Drive it like this:
1. start() — generate the verification URL
python3 - <<'EOF'
import json, sys
sys.path.insert(0, '/data/workspace/skills/xai-grok-onboarding')
from exports import start
print(json.dumps(start(), indent=2))
EOFReturns verification_url_with_code — tell the user to open it in their browser, log in (if needed), and click Approve.
⚠️ Wait for explicit user confirmation before calling poll(). Polling too eagerly burns tokens for a "still pending" state.
2. poll() — confirm approval (after the user says "done")
python3 - <<'EOF'
import json, sys
sys.path.insert(0, '/data/workspace/skills/xai-grok-onboarding')
from exports import poll
print(json.dumps(poll(), indent=2))
EOFThree terminal outcomes:
status="connected"→ success; show the userdefault_model_idto switch tostatus="pending"→ user hasn't approved yet; ask them to confirm before re-pollingok=falsewithaccess_denied→ see "account gate" section aboveok=falsewithexpired→ device code timed out (15 min); callstart()again
3. After successful connect — tell the user
When poll() returns status='connected', the first thing you must do is tell the user:
"Connection successful. Please refresh your browser page — once it reloads, the new xai-grok/* models will appear in the model picker."The web frontend caches the model list client-side and does not auto-refresh after an OAuth connect completes. Without a manual page refresh the user will not see their newly available models and will think the connection failed. Always include this instruction in your reply — do not assume the picker updates on its own.
After the refresh, the default model is xai-grok/grok-4.3. Other available models depend on the subscription tier (SuperGrok Heavy unlocks grok-build-0.1).
To switch: /model xai-grok/grok-4.3 or use the picker.
---
Function reference
| Function | Args | Returns |
|---|---|---|
status() | — | Current credential state + available models + expiry |
start() | — | Device code prompt: {verification_url_with_code, user_code, expires_in_seconds} |
poll(pending_id=None) | optional pending_id | {status: connected/pending} + credential info |
logout() | — | Delete credential + flush agent cache |
refresh() | — | Force-refresh access token (debug; normally automatic) |
models(force=False) | — | List available models from the OAuth endpoint |
force=True on models bypasses the cache TTL.
All functions return a dict with ok: True on success or ok: False, error: "..." on failure.
---
After connecting
Models surface with the xai-grok/ prefix:
xai-grok/grok-4.3— primary chat model (default)xai-grok/grok-build-0.1— Grok Build coding model (SuperGrok Heavy tier only)xai-grok/grok-4.20-0309-reasoning— reasoning variantxai-grok/grok-4.20-0309-non-reasoning— faster, no reasoningxai-grok/grok-4.20-multi-agent-0309— multi-agent variant (uses /v1/responses internally)
User switches via /model xai-grok/grok-4.3 or the model picker UI.
Lane routing (transparent)
The provider auto-routes based on model id:
- Multi-agent models →
https://api.x.ai/v1/responses(Responses API) - All other Grok models →
https://api.x.ai/v1/chat/completions(OpenAI-compatible)
Users do not need to know which dialect each model speaks — passing the standard messages=[...] shape works for both. For multi-agent, an optional thinking={"effort": "low"|"medium"|"high"} controls how many agents collaborate.
Subsequent chat calls hit https://api.x.ai/v1 directly using the OAuth bearer — bypasses the platform proxy. Subscription usage limits apply (not the platform credit balance). Image / video models (grok-imagine-*) are filtered out of the chat picker but accessible via image generation tools.
---
xAI's OAuth allowlist — biggest cause of "frontend rejected my login"
Critical context (verified from independent implementations, 2026-05): xAI's backend maintains an internal allowlist on the OAuth API surface and frequently returns HTTP 403 to OAuth tokens even when the user's subscription is active and the device-code flow itself succeeded. This is documented by Hermes Agent (their xAI OAuth troubleshooting) and confirmed by independent OpenCode plugin authors. It is not a bug in our adapter — switching to PKCE flow does not bypass it (both grant types hit the same allowlist).
Subscription tier ↔ OAuth availability (observed, not guaranteed)
| Subscription | Price | OAuth typical outcome |
|---|---|---|
| X Premium (X.com only) | $8 /mo | ❌ Often rejected — not enough entitlement |
| X Premium+ (linked X account) | $16 /mo | ✅ Usually works — xAI auto-links via X account |
| SuperGrok (grok.com direct) | $30 /mo | ⚠️ Mixed — some users blocked, some pass |
| SuperGrok Heavy | $300 /mo | ✅ Reliably works |
Preflight before starting the flow
If the user has a standalone X Premium subscription (not Premium+) or a SuperGrok base subscription and is hitting frontend rejection, tell them to do this first, in order:
1. Go to https://grok.com → Settings → Account → "Connect your X Account" (links the two identities so xAI can verify subscription status) 2. Make sure the linked X account is the one carrying the Premium / Premium+ tag 3. Sign out + sign back in at https://accounts.x.ai once after linking (refreshes the entitlement) 4. Retry the device-code flow
A large share of reported "OAuth login fails / device code never accepted" cases trace back to missing X account link — xAI's backend can't verify the Premium status without the link, and the front door silently rejects the device code.
When OAuth still fails after preflight → recommend BYOK
The standard fix in the community (Hermes, OpenCode plugin, etc.) is stop fighting OAuth, switch to API key:
- The
XAI_API_KEYpath uses a different xAI backend (console.x.ai pay-per-token) that is not subject to the OAuth allowlist. - Tell the user to grab a key at https://console.x.ai and then route them to the
byok-custom-modelskill with the xAI template. Setup is two minutes. - Trade-off: BYOK bills per-token from a separate wallet instead of using the subscription quota — explain that clearly so the user picks consciously.
What we do NOT do
- ❌ Spoof referrer / pretend to be another known client (Hermes, grok-cli, etc.) — bypassing entitlement is against ToS and xAI may tighten the allowlist further
- ❌ Retry the device-code flow on
access_denied— it will keep failing; recommend preflight or BYOK instead - ❌ Tell the user it's a Starchild bug — it's an xAI account-gating decision we cannot override
---
Limits & BYOK fallback
xAI does not publish exact daily caps or RPM the way OpenAI does. Practical reality for OAuth-backed Grok usage:
- No published hard numbers. Limits are fair-use based — xAI throttles temporarily if you generate massive volume in a short window.
- Tier matters. Heavy ($300/mo) gives the highest consumer ceiling — significantly above SuperGrok ($30) or X Premium+. Standard text/chat rarely hits the cap for normal-to-heavy daily use.
- Image / video / voice have tighter quotas. E.g. Heavy users get roughly ~80+ video generations per 12 hours, but these can still throttle during peak load.
- Soft ceiling signal: if the user starts seeing
429or "rate limit exceeded" messages from xAI, they've hit the fair-use ceiling for that feature. Wait it out (limits reset on a rolling window, not a fixed daily clock) or switch billing modes.
When to suggest switching to BYOK
If the user wants predictable, spend-based high-volume access — or if they're hitting limits often — they can switch to a regular xAI API key:
1. Get a key at https://console.x.ai (pay-per-token, separate from subscription) 2. Use byok-custom-model skill with the xAI template 3. BYOK models appear alongside OAuth models in the picker — switch per turn
Both paths can coexist; the user picks per request. OAuth = subscription quota (good default). BYOK = clearer rate limits + per-token billing (good for heavy automation or when you want cost transparency).
Error → action table
| Symptom | Cause | Action |
|---|---|---|
429 / "rate limit exceeded" on chat | OAuth fair-use ceiling hit | Wait for soft reset, or switch this request to BYOK |
403 / access_denied at OAuth time | xAI account gated | Use BYOK (see preflight section above) |
Repeated 401 after refresh() | Token revoked / subscription canceled | logout() + restart, or switch to BYOK |
503 / 5xx upstream | xAI infra issue | Retry shortly. BYOK uses the same upstream, won't help |
---
Reauth
Tokens auto-refresh via refresh_token (6h access token TTL — relatively generous vs Codex's 1h). If a 401 surfaces: 1. refresh() — try the manual refresh path 2. If still failing, logout() + restart from start()
---
Critical rules
- Never paste user_code into the verification URL field for the user. The URL
accounts.x.ai/oauth2/device?user_code=XXXXalready embeds the code — just open it. - Never start the flow without explicit user request. "I want to use Grok" needs a follow-up question about subscription vs. API key; "use my SuperGrok subscription" is enough.
- Wait for user confirmation between `start` and `poll`. Auto-polling wastes API calls and produces stale "pending" responses.
- On `access_denied`, do NOT retry blindly. Explain the gate, suggest BYOK fallback.
- Never log or echo the access_token / refresh_token. They're persistent credentials. The exports never include them in return values either.
"""xAI Grok OAuth skill — script-mode exports.
Mirrors skills/chatgpt-codex-onboarding/exports.py:
status | start | poll | logout | refresh | models
File-backed pending state:
Each script subprocess starts fresh, so the DeviceCodePrompt from start()
must be persisted to workspace/.xai_oauth_pending.json before poll() can
resume it. Mirrors the codex skill exactly.
Wire model:
- Read paths (status, models): in-process, no HTTP needed
- Write paths (start, poll, logout, refresh): touch on-disk credential via
core.xai_grok.store, then call /internal/runtime/flush_agent_cache on
loopback so AgentManager rebuilds providers next turn
"""
from __future__ import annotations
import asyncio
import json
import os
import sys
import tempfile
import time
from typing import Any, Dict, Optional
# --------------------------------------------------------------------------
# sys.path bootstrap (same as codex skill) + backend availability check
# --------------------------------------------------------------------------
#
# History: in the window 2026-05-22 → 2026-05-26 the xai_grok backend
# was published to skills/ but the corresponding core/xai_grok module
# was briefly reverted from the platform image (commit db5ad17). Skills
# called from older images crashed with bare
# ModuleNotFoundError: No module named 'core.xai_grok'
# and the agent had no idea why (issue #161).
#
# Strategy now:
# 1. _bootstrap_clawd_path returns a (found, checked_paths) tuple so
# we can build an honest diagnostic if nothing matched.
# 2. The top-level `from core.xai_grok import ...` is wrapped in
# try/except. On failure we record the exception and let every
# public function return a structured "backend unavailable"
# response with a concrete next-step. The module still imports
# cleanly, so skill discovery doesn't break.
# 3. Backend-dependent type names referenced in annotations are safe
# because the file uses `from __future__ import annotations`
# (annotations are strings, not evaluated at import time).
def _bootstrap_clawd_path():
"""Locate the starchild-clawd checkout containing core/xai_grok.
Returns (found: bool, checked_paths: List[str]). On success, the
matching directory is prepended to sys.path so `from core.xai_grok
import ...` resolves. On failure callers can report which paths
were searched so users know where to point STARCHILD_CLAWD_DIR.
"""
candidates = []
env_override = os.environ.get("STARCHILD_CLAWD_DIR")
if env_override:
candidates.append(env_override)
candidates.extend(["/app", "/data/workspace/starchild-clawd"])
for cand in candidates:
marker = os.path.join(cand, "core", "xai_grok", "__init__.py")
if os.path.exists(marker):
if cand not in sys.path:
sys.path.insert(0, cand)
return True, candidates
return False, candidates
_CLAWD_FOUND, _CLAWD_CHECKED = _bootstrap_clawd_path()
_BACKEND_IMPORT_ERROR = None # type: ignore[assignment]
if _CLAWD_FOUND:
try:
from core.xai_grok import ( # noqa: E402
AccountAccessDenied,
DeviceCodePrompt,
DeviceCodeTimeout,
OAuthState,
ReauthRequired,
VERIFY_URL,
XaiGrokOAuthError,
classify_state,
delete_credential,
fetch_models_from_api,
load_credential,
poll_authorization,
refresh_access_token,
request_device_code,
resolve_xai_models,
save_credential,
)
except (ImportError, AttributeError) as _e:
# Module exists but is missing expected symbols — partial deploy
# or version skew. Defer the error to call time so skill
# discovery still works.
_BACKEND_IMPORT_ERROR = _e
else:
# The whole xai_grok package is absent. Most common cause:
# platform image predates 2026-05-22 (xAI OAuth backend launch).
_BACKEND_IMPORT_ERROR = ModuleNotFoundError(
"core.xai_grok not found in any of: " + ", ".join(_CLAWD_CHECKED)
)
def _backend_unavailable_response() -> Dict[str, Any]:
"""Structured error returned by every public method when the
backend can't be loaded. Tells the agent exactly what to do next
instead of crashing with a raw ModuleNotFoundError.
"""
return {
"ok": False,
"error": "xai_oauth_backend_unavailable",
"detail": f"{type(_BACKEND_IMPORT_ERROR).__name__}: {_BACKEND_IMPORT_ERROR}",
"checked_paths": _CLAWD_CHECKED,
"hint": (
"The xAI OAuth backend (core.xai_grok) is not available in this "
"platform image. Two options: "
"(1) Update the platform image — xAI OAuth shipped 2026-05-22; "
"older images don't have it. "
"(2) Fall back to BYOK API key via the byok-custom-model skill "
"(get a key from https://console.x.ai)."
),
}
def _require_backend(fn):
"""Decorator: short-circuit to a structured error if the backend
failed to import. Keeps every public function one-liner."""
def wrapper(*args, **kwargs):
if _BACKEND_IMPORT_ERROR is not None:
return _backend_unavailable_response()
return fn(*args, **kwargs)
wrapper.__name__ = fn.__name__
wrapper.__doc__ = fn.__doc__
return wrapper
# --------------------------------------------------------------------------
# Cache-flush helper
# --------------------------------------------------------------------------
def _api_base() -> str:
return f"http://localhost:{os.environ.get('PORT', '8000')}"
def _flush_cache() -> Dict[str, Any]:
"""Force AgentManager to rebuild providers next call."""
import urllib.request
import urllib.error
url = _api_base() + "/internal/runtime/flush_agent_cache"
req = urllib.request.Request(
url, data=b"{}",
headers={"Content-Type": "application/json"},
method="POST",
)
try:
with urllib.request.urlopen(req, timeout=10) as resp:
try:
return json.loads(resp.read().decode("utf-8", errors="replace"))
except json.JSONDecodeError:
return {"ok": False, "reason": "non_json_response"}
except urllib.error.HTTPError as e:
return {"ok": False, "reason": f"http_{e.code}", "detail": str(e)}
except Exception as e:
return {"ok": False, "reason": type(e).__name__, "detail": str(e)}
# --------------------------------------------------------------------------
# File-backed pending-flow store
# --------------------------------------------------------------------------
def _pending_path() -> str:
workspace = os.environ.get("WORKSPACE_DIR", "/data/workspace")
return os.path.join(workspace, ".xai_oauth_pending.json")
def _save_pending(prompt: DeviceCodePrompt) -> None:
expires_at = time.time() + prompt.expires_in_seconds
payload = {
"device_code": prompt.device_code,
"user_code": prompt.user_code,
"verification_url": prompt.verification_url,
"verification_url_with_code": prompt.verification_url_with_code,
"interval_seconds": prompt.interval_seconds,
"expires_at": expires_at,
}
path = _pending_path()
tmp_fd, tmp_path = tempfile.mkstemp(dir=os.path.dirname(path) or ".", prefix=".oauth_pending_")
try:
with os.fdopen(tmp_fd, "w") as f:
json.dump(payload, f)
os.chmod(tmp_path, 0o600)
os.replace(tmp_path, path)
except Exception:
try:
os.unlink(tmp_path)
except OSError:
pass
raise
def _load_pending(pending_id: Optional[str]) -> Optional[DeviceCodePrompt]:
path = _pending_path()
if not os.path.exists(path):
return None
try:
with open(path) as f:
raw = json.load(f)
except (OSError, json.JSONDecodeError):
return None
if raw.get("expires_at", 0) < time.time():
try:
os.unlink(path)
except OSError:
pass
return None
if pending_id and raw.get("user_code") != pending_id:
return None
expires_in_seconds = max(1, int(raw.get("expires_at", 0) - time.time()))
return DeviceCodePrompt(
device_code=raw["device_code"],
user_code=raw["user_code"],
verification_url=raw["verification_url"],
verification_url_with_code=raw["verification_url_with_code"],
interval_seconds=int(raw.get("interval_seconds", 5)),
expires_in_seconds=expires_in_seconds,
)
def _clear_pending() -> None:
try:
os.unlink(_pending_path())
except OSError:
pass
# --------------------------------------------------------------------------
# Tiny asyncio runner
# --------------------------------------------------------------------------
def _run(coro):
return asyncio.new_event_loop().run_until_complete(coro)
# --------------------------------------------------------------------------
# Public API
# --------------------------------------------------------------------------
@_require_backend
def status() -> Dict[str, Any]:
"""Inspect the current OAuth credential state."""
cred = load_credential()
st = classify_state(cred)
if cred is None:
return {
"ok": True,
"state": st.value,
"connected": False,
"verification_url_root": VERIFY_URL,
"hint": "Not connected. Run start() to log in with your xAI / X Premium / SuperGrok account.",
}
now = int(time.time())
slugs = resolve_xai_models(cached_models=cred.available_models)
tier_label = {
1: "X Premium",
2: "X Premium+",
3: "SuperGrok",
4: "SuperGrok Heavy",
}.get(cred.tier or 0)
return {
"ok": True,
"state": st.value,
"connected": st in {OAuthState.CONNECTED, OAuthState.EXPIRING},
"email": cred.email,
"account_id": cred.account_id,
"tier": cred.tier,
"tier_label": tier_label,
"expires_at": cred.expires_at,
"expires_in_seconds": max(0, cred.expires_at - now),
"updated_at": cred.updated_at,
"models_count": len(slugs),
"models_fetched_at": cred.models_fetched_at,
"default_model_id": f"xai-grok/{slugs[0]}" if slugs else None,
"available_models": [f"xai-grok/{s}" for s in slugs],
}
@_require_backend
def start() -> Dict[str, Any]:
"""Step 1: ask xAI for a device-code prompt."""
try:
prompt = _run(request_device_code())
except XaiGrokOAuthError as e:
return {"ok": False, "error": str(e)}
_save_pending(prompt)
return {
"ok": True,
"status": "pending",
"pending_id": prompt.user_code,
"user_code": prompt.user_code,
"verification_url": prompt.verification_url,
"verification_url_with_code": prompt.verification_url_with_code,
"interval_seconds": prompt.interval_seconds,
"expires_in_seconds": prompt.expires_in_seconds,
"instructions": (
f"Open {prompt.verification_url_with_code} in a browser already "
f"logged in to your xAI / SuperGrok account, click Approve. "
f"User code is pre-filled ({prompt.user_code}) — no need to type it. "
"After approving, ask the agent to poll."
),
}
@_require_backend
def poll(pending_id: Optional[str] = None) -> Dict[str, Any]:
"""Step 2: poll xAI to see if the user finished authorizing.
Returns:
- status='pending': call poll again later
- status='connected': credential saved, model now selectable
- ok=false with error mentioning access_denied: account gated by xAI
(suggest BYOK API key fallback)
- ok=false with expired: device code timed out (15 min); restart with start()
"""
prompt = _load_pending(pending_id)
if prompt is None:
return {
"ok": False,
"error": "Unknown or expired pending flow; restart with start().",
"hint": "If the user hasn't run start() yet, do that first.",
}
try:
cred = _run(poll_authorization(
prompt, deadline=time.time() + 8.0
))
except DeviceCodeTimeout:
# xAI returned authorization_pending several times within our short
# poll window — keep the prompt for next call.
return {
"ok": True,
"status": "pending",
"message": "Still waiting for the user to authorize. Call poll again.",
}
except AccountAccessDenied as e:
_clear_pending()
return {
"ok": False,
"error": f"access_denied: {e}",
"hint": (
"xAI rejected the OAuth grant. This is usually an account-level "
"gate (xAI does not whitelist every SuperGrok account for "
"third-party OAuth). Confirm subscription is active, try the "
"verification URL in your already-logged-in browser, or fall "
"back to BYOK API key via the byok-custom-model skill "
"(key from https://console.x.ai)."
),
}
except XaiGrokOAuthError as e:
_clear_pending()
return {"ok": False, "error": str(e)}
_clear_pending()
# Warm models cache inline — best effort, never blocks connection.
try:
_run(_refresh_models_cache(cred))
except Exception:
pass
cred = load_credential() or cred
slugs = resolve_xai_models(cached_models=cred.available_models)
flush_result = _flush_cache()
return {
"ok": True,
"status": "connected",
"email": cred.email,
"account_id": cred.account_id,
"expires_at": cred.expires_at,
"models_count": len(slugs),
"default_model_id": f"xai-grok/{slugs[0]}" if slugs else None,
"available_models": [f"xai-grok/{s}" for s in slugs],
"cache_flush": flush_result,
"next_step": (
"Connection successful. Switch with "
f"`/model xai-grok/{slugs[0] if slugs else 'grok-4.3'}` "
"or use the model picker."
),
}
@_require_backend
def logout() -> Dict[str, Any]:
"""Disconnect — delete the on-disk credential file and flush cache."""
existed = delete_credential()
flush_result = _flush_cache()
return {
"ok": True,
"status": "disconnected" if existed else "not_connected",
"removed": existed,
"cache_flush": flush_result,
}
@_require_backend
def refresh() -> Dict[str, Any]:
"""Force-refresh the access token. Normally automatic — debug only."""
cred = load_credential()
if cred is None:
return {"ok": False, "error": "Not connected; nothing to refresh."}
try:
updated = _run(refresh_access_token(cred))
except ReauthRequired as e:
return {"ok": False, "error": f"reauth required: {e}",
"hint": "Run logout() then start() to re-authenticate."}
except Exception as e:
return {"ok": False, "error": f"refresh failed: {e}"}
return {
"ok": True,
"status": "refreshed",
"expires_at": updated.expires_at,
"expires_in_seconds": max(0, updated.expires_at - int(time.time())),
}
@_require_backend
def models(force: bool = False) -> Dict[str, Any]:
"""List the OAuth subscription's available models.
Cached on the credential file; pass force=True to bypass the TTL.
"""
cred = load_credential()
if cred is None:
return {"ok": False, "error": "Not connected."}
if force or not cred.available_models:
try:
api_models = _run(_refresh_models_cache(cred))
except Exception as e:
return {"ok": False, "error": f"fetch failed: {e}"}
else:
api_models = cred.available_models
slugs = resolve_xai_models(cached_models=api_models)
return {
"ok": True,
"source": "api" if force else "cache",
"count": len(slugs),
"available_models": [f"xai-grok/{s}" for s in slugs],
"fetched_at": cred.models_fetched_at,
}
# --------------------------------------------------------------------------
# Internal helpers
# --------------------------------------------------------------------------
async def _refresh_models_cache(cred):
api_models = await fetch_models_from_api(cred.access_token)
if not api_models:
return []
cred.available_models = list(api_models)
cred.models_fetched_at = int(time.time())
save_credential(cred)
return api_models
Related skills
FAQ
What is xai-grok-onboarding?
|
When should I use xai-grok-onboarding?
|
Is xai-grok-onboarding safe to install?
Review the Security Audits panel on this page before production use.