{"slug": "show-your-openai-codex-rate-limit-reset-credits-with-live-expiry-countdown", "title": "Show your OpenAI Codex rate-limit reset credits with live expiry countdown", "summary": "A developer created a Python script that displays OpenAI Codex rate-limit reset credits with a live expiry countdown. The tool reads authentication from the Codex CLI's auth file and calls the OpenAI API to fetch credit data, showing available credits, next expiration, and a progress bar.", "body_md": "| #!/usr/bin/env python3 | |\n| \"\"\"Show your OpenAI Codex rate-limit reset credits. | |\n| Reads authentication from ~/.codex/auth.json (created by the Codex CLI). | |\n| The auth file is never transmitted anywhere except OpenAI's own API. | |\n| \"\"\" | |\n| import argparse | |\n| import json | |\n| import urllib.request | |\n| from datetime import datetime, timedelta, timezone | |\n| from pathlib import Path | |\n| # ANSI styles | |\n| BOLD = \"\\033[1m\" | |\n| DIM = \"\\033[2m\" | |\n| GREEN = \"\\033[32m\" | |\n| YELLOW = \"\\033[33m\" | |\n| RED = \"\\033[31m\" | |\n| CYAN = \"\\033[36m\" | |\n| RESET = \"\\033[0m\" | |\n| API_URL = \"https://chatgpt.com/backend-api/wham/rate-limit-reset-credits\" | |\n| def load_auth(auth_path: Path) -> dict: | |\n| \"\"\"Load and return the Codex auth JSON.\"\"\" | |\n| if not auth_path.exists(): | |\n| raise FileNotFoundError(f\"Auth file not found: {auth_path}\") | |\n| return json.loads(auth_path.read_text()) | |\n| def fetch_credits(auth: dict) -> dict: | |\n| \"\"\"Call the Codex rate-limit reset credits endpoint.\"\"\" | |\n| token = auth[\"tokens\"][\"access_token\"] | |\n| account = auth[\"tokens\"][\"account_id\"] | |\n| req = urllib.request.Request( | |\n| API_URL, | |\n| headers={ | |\n| \"Authorization\": f\"Bearer {token}\", | |\n| \"ChatGPT-Account-ID\": account, | |\n| \"OpenAI-Beta\": \"codex-1\", | |\n| \"originator\": \"Codex Desktop\", | |\n| }, | |\n| ) | |\n| with urllib.request.urlopen(req) as resp: | |\n| return json.loads(resp.read()) | |\n| def parse_dt(iso: str | None) -> datetime | None: | |\n| if not iso: | |\n| return None | |\n| return datetime.fromisoformat(iso.replace(\"Z\", \"+00:00\")) | |\n| def fmt_dt(dt: datetime | None) -> str: | |\n| if not dt: | |\n| return \"n/a\" | |\n| return dt.strftime(\"%b %d, %H:%M UTC\") | |\n| def time_left(dt: datetime | None) -> str: | |\n| if not dt: | |\n| return \"\" | |\n| delta = dt - datetime.now(timezone.utc) | |\n| if delta <= timedelta(0): | |\n| return f\"{RED}expired{RESET}\" | |\n| days = delta.days | |\n| hours, remainder = divmod(delta.seconds, 3600) | |\n| minutes, _ = divmod(remainder, 60) | |\n| if days > 0: | |\n| return f\"{GREEN}{days}d {hours}h left{RESET}\" | |\n| if hours > 0: | |\n| return f\"{YELLOW}{hours}h {minutes}m left{RESET}\" | |\n| return f\"{RED}{minutes}m left{RESET}\" | |\n| def progress_bar(available: int, total: int, width: int = 18) -> str: | |\n| if total <= 0: | |\n| return f\"{DIM}{'░' * width}{RESET}\" | |\n| filled = min(width, max(0, int(round(available / total * width)))) | |\n| empty = width - filled | |\n| if empty: | |\n| return f\"{GREEN}{'█' * filled}{DIM}{'░' * empty}{RESET}\" | |\n| return f\"{GREEN}{'█' * filled}{RESET}\" | |\n| def render(credits_data: dict) -> None: | |\n| credits = credits_data.get(\"credits\", []) | |\n| available = credits_data.get(\"available_count\", 0) | |\n| earned = credits_data.get(\"total_earned_count\", 0) | |\n| total = len(credits) | |\n| print() | |\n| print(f\" {BOLD}{CYAN}🐳 Codex Rate-Limit Reset Credits{RESET}\") | |\n| print() | |\n| print( | |\n| f\" {progress_bar(available, total)}\" | |\n| f\" {BOLD}{available}{RESET}{DIM}/{total} available{RESET}\" | |\n| f\" · earned: {earned}\" | |\n| ) | |\n| if not credits: | |\n| print(f\"\\n {YELLOW}⚠️ No credits found.{RESET}\\n\") | |\n| return | |\n| sorted_credits = sorted( | |\n| credits, | |\n| key=lambda c: ( | |\n| parse_dt(c.get(\"expires_at\")) or datetime.max.replace(tzinfo=timezone.utc) | |\n| ), | |\n| ) | |\n| next_expires = parse_dt(sorted_credits[0].get(\"expires_at\")) | |\n| print() | |\n| print( | |\n| f\" {BOLD}Next expires:{RESET} {fmt_dt(next_expires)} · {time_left(next_expires)}\" | |\n| ) | |\n| print() | |\n| for idx, credit in enumerate(sorted_credits, start=1): | |\n| status = credit.get(\"status\", \"unknown\") | |\n| expires = parse_dt(credit.get(\"expires_at\")) | |\n| granted = parse_dt(credit.get(\"granted_at\")) | |\n| if status == \"available\": | |\n| icon = \"●\" | |\n| status_color = GREEN | |\n| elif status == \"redeemed\": | |\n| icon = \"✓\" | |\n| status_color = DIM | |\n| else: | |\n| icon = \"!\" | |\n| status_color = YELLOW | |\n| print( | |\n| f\" {status_color}{icon}{RESET} {BOLD}#{idx}{RESET}\" | |\n| f\" {status_color}{status}{RESET}\" | |\n| f\" · expires {fmt_dt(expires)} · {time_left(expires)}\" | |\n| ) | |\n| print(f\" {DIM}granted {fmt_dt(granted)}{RESET}\") | |\n| print() | |\n| def main() -> None: | |\n| parser = argparse.ArgumentParser( | |\n| description=\"Show your OpenAI Codex rate-limit reset credits.\", | |\n| ) | |\n| parser.add_argument( | |\n| \"--auth\", | |\n| type=Path, | |\n| default=Path(\"~/.codex/auth.json\").expanduser(), | |\n| help=\"Path to your Codex auth.json file (default: ~/.codex/auth.json)\", | |\n| ) | |\n| args = parser.parse_args() | |\n| auth = load_auth(args.auth) | |\n| credits_data = fetch_credits(auth) | |\n| render(credits_data) | |\n| if __name__ == \"__main__\": | |\n| main() |", "url": "https://wpnews.pro/news/show-your-openai-codex-rate-limit-reset-credits-with-live-expiry-countdown", "canonical_source": "https://gist.github.com/ayagmar/0c3a4bdc0ccb4e44c3d36bce73819759", "published_at": "2026-06-19 15:10:46+00:00", "updated_at": "2026-06-19 17:06:42.283239+00:00", "lang": "en", "topics": ["developer-tools", "large-language-models", "ai-tools"], "entities": ["OpenAI", "Codex", "ChatGPT"], "alternates": {"html": "https://wpnews.pro/news/show-your-openai-codex-rate-limit-reset-credits-with-live-expiry-countdown", "markdown": "https://wpnews.pro/news/show-your-openai-codex-rate-limit-reset-credits-with-live-expiry-countdown.md", "text": "https://wpnews.pro/news/show-your-openai-codex-rate-limit-reset-credits-with-live-expiry-countdown.txt", "jsonld": "https://wpnews.pro/news/show-your-openai-codex-rate-limit-reset-credits-with-live-expiry-countdown.jsonld"}}