Check remaining ChatGPT Codex invitation reset credits and expiry
#ChatGPT
Check how many ChatGPT Codex invitation reset credits remain and when they expire. The main invitation campaign has ended; check the rewards endpoint before using an old invitation flow.
The briefing
Quick conclusion
- The main Codex invitation campaign has ended. An available entry point does not mean you can still earn reset credits.
- This browser tool checks your account's rate_limit_reset_credits, available count and expiration times.
- If you searched for “Codex reset credits” or “ChatGPT invite rewards”, first check whether the rewards endpoint still returns available credits before continuing.
- If OpenAI restores invitation rewards, this page will be updated with available entry points and conditions.
0. Chrome extension
Example:

If you prefer not to use the extension, follow the steps below.
1. Sign in to ChatGPT in your browser
2. Open the F12 Console and enter the following code
(async () => {
const PANEL_ID = "codex-referral-panel";
const REFERRAL_KEY = "codex_referral_persistent_invite";
const API_BASE = "/backend-api";
document.getElementById(PANEL_ID)?.remove();
if (location.hostname !== "chatgpt.com") {
throw new Error("Open https://chatgpt.com first, then run this in that page's F12 Console.");
}
const panel = document.createElement("section");
panel.id = PANEL_ID;
panel.innerHTML = `
<style>
#${PANEL_ID} {
position: fixed;
z-index: 2147483647;
right: 20px;
bottom: 20px;
width: min(820px, calc(100vw - 32px));
max-height: calc(100vh - 40px);
color: #171717;
background: #fff;
border: 1px solid #d4d4d4;
border-radius: 8px;
box-shadow: 0 16px 48px rgba(0, 0, 0, .22);
font: 14px/1.5 ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
"Segoe UI", sans-serif;
overflow: hidden;
}
#${PANEL_ID} * { box-sizing: border-box; }
#${PANEL_ID} header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 14px 16px;
color: #fff;
background: #171717;
}
#${PANEL_ID} h2 {
margin: 0;
font-size: 15px;
font-weight: 650;
letter-spacing: 0;
}
#${PANEL_ID} .close {
width: 28px;
height: 28px;
padding: 0;
color: #fff;
background: transparent;
border: 0;
font-size: 20px;
cursor: pointer;
}
#${PANEL_ID} .body {
max-height: calc(100vh - 96px);
padding: 16px;
overflow: auto;
}
#${PANEL_ID} .summary {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
margin-bottom: 14px;
}
#${PANEL_ID} .metric {
padding: 9px 10px;
background: #f5f5f5;
border: 1px solid #e5e5e5;
border-radius: 6px;
}
#${PANEL_ID} .metric span {
display: block;
color: #737373;
font-size: 12px;
}
#${PANEL_ID} .metric strong {
display: block;
margin-top: 2px;
font-size: 14px;
overflow-wrap: anywhere;
}
#${PANEL_ID} .section-title {
margin: 14px 0 8px;
font-size: 13px;
font-weight: 700;
}
#${PANEL_ID} .table-wrap {
max-height: 210px;
margin-bottom: 14px;
overflow: auto;
border: 1px solid #e5e5e5;
border-radius: 6px;
}
#${PANEL_ID} table {
width: 100%;
min-width: 680px;
border-collapse: collapse;
background: #fff;
}
#${PANEL_ID} th,
#${PANEL_ID} td {
padding: 8px 9px;
border-bottom: 1px solid #f0f0f0;
text-align: left;
vertical-align: top;
font-size: 12px;
}
#${PANEL_ID} th {
position: sticky;
top: 0;
z-index: 1;
color: #525252;
background: #fafafa;
font-weight: 700;
}
#${PANEL_ID} td {
color: #262626;
overflow-wrap: anywhere;
}
#${PANEL_ID} a {
color: #2563eb;
text-decoration: none;
}
#${PANEL_ID} a:hover { text-decoration: underline; }
#${PANEL_ID} .empty {
color: #737373;
text-align: center;
}
#${PANEL_ID} label {
display: block;
margin-bottom: 6px;
font-weight: 600;
}
#${PANEL_ID} input {
width: 100%;
height: 40px;
padding: 0 11px;
color: #171717;
background: #fff;
border: 1px solid #a3a3a3;
border-radius: 6px;
outline: none;
}
#${PANEL_ID} input:focus {
border-color: #171717;
box-shadow: 0 0 0 2px rgba(23, 23, 23, .12);
}
#${PANEL_ID} .actions {
display: flex;
gap: 8px;
margin-top: 10px;
}
#${PANEL_ID} button.action {
height: 38px;
padding: 0 13px;
border: 1px solid #a3a3a3;
border-radius: 6px;
background: #fff;
cursor: pointer;
font-weight: 600;
}
#${PANEL_ID} button.primary {
flex: 1;
color: #fff;
background: #171717;
border-color: #171717;
}
#${PANEL_ID} button:disabled {
cursor: not-allowed;
opacity: .5;
}
#${PANEL_ID} .status {
min-height: 72px;
max-height: 220px;
margin: 14px 0 0;
padding: 12px;
overflow: auto;
overflow-wrap: anywhere;
color: #262626;
background: #fafafa;
border: 1px solid #e5e5e5;
border-left: 4px solid #a3a3a3;
border-radius: 6px;
}
#${PANEL_ID} .status.ok {
color: #14532d;
background: #f0fdf4;
border-color: #bbf7d0;
border-left-color: #16a34a;
}
#${PANEL_ID} .status.error {
color: #7f1d1d;
background: #fef2f2;
border-color: #fecaca;
border-left-color: #dc2626;
}
#${PANEL_ID} .status.warn {
color: #713f12;
background: #fffbeb;
border-color: #fde68a;
border-left-color: #f59e0b;
}
#${PANEL_ID} .status-title {
margin: 0 0 6px;
font-size: 14px;
font-weight: 750;
}
#${PANEL_ID} .status-line {
margin-top: 3px;
font-size: 13px;
line-height: 1.45;
}
</style>
<header>
<h2>Check and send Codex invitations</h2>
<button class="close" title="Close" aria-label="Close">×</button>
</header>
<div class="body">
<div class="summary">
<div class="metric"><span>Current browser plan</span><strong data-field="plan">Loading</strong></div>
<div class="metric"><span>Invitations remaining</span><strong data-field="quota">Loading</strong></div>
<div class="metric"><span>Reset credits</span><strong data-field="credits">Loading</strong></div>
<div class="metric"><span>Account ID</span><strong data-field="account">Loading</strong></div>
</div>
<div class="section-title">Sent invitation details</div>
<div class="table-wrap">
<table>
<thead>
<tr>
<th>Email</th>
<th>Status</th>
<th>Expires at</th>
<th>Reward</th>
<th>Invitation link</th>
</tr>
</thead>
<tbody data-field="referrals">
<tr><td class="empty" colspan="5">Loading</td></tr>
</tbody>
</table>
</div>
<div class="section-title">My reset credit rewards</div>
<div class="table-wrap">
<table>
<thead>
<tr>
<th>Source</th>
<th>Status</th>
<th>Granted at</th>
<th>Expires at</th>
<th>Used at</th>
</tr>
</thead>
<tbody data-field="reward-credits">
<tr><td class="empty" colspan="5">Loading</td></tr>
</tbody>
</table>
</div>
<label for="codex-referral-email">Invitee email</label>
<input id="codex-referral-email" type="email" autocomplete="off"
placeholder="[email protected]">
<div class="actions">
<button class="action" data-action="refresh">Refresh status</button>
<button class="action primary" data-action="send" disabled>Send invitation</button>
</div>
<div class="status">Reading the current browser session…</div>
</div>
`;
document.body.appendChild(panel);
const $ = (selector) => panel.querySelector(selector);
const planEl = $('[data-field="plan"]');
const quotaEl = $('[data-field="quota"]');
const creditsEl = $('[data-field="credits"]');
const accountEl = $('[data-field="account"]');
const referralsEl = $('[data-field="referrals"]');
const rewardCreditsEl = $('[data-field="reward-credits"]');
const emailEl = $("#codex-referral-email");
const refreshButton = $('[data-action="refresh"]');
const sendButton = $('[data-action="send"]');
const statusEl = $(".status");
let token = "";
let accountId = "";
let busy = false;
let inviteQuotaRemaining = 0;
const setStatus = (message, type = "") => {
statusEl.className = `status ${type}`.trim();
statusEl.replaceChildren();
const lines = Array.isArray(message) ? message : String(message ?? "").split("\n");
const title = lines.shift() || "";
const titleEl = document.createElement("div");
titleEl.className = "status-title";
titleEl.textContent = title;
statusEl.appendChild(titleEl);
for (const line of lines.filter(Boolean)) {
const lineEl = document.createElement("div");
lineEl.className = "status-line";
lineEl.textContent = line;
statusEl.appendChild(lineEl);
}
};
const setBusy = (value) => {
busy = value;
refreshButton.disabled = value;
emailEl.disabled = value;
sendButton.disabled = value || !token || !accountId;
};
const formatTime = (iso) => {
if (!iso) return "";
return new Intl.DateTimeFormat("zh-CN", {
timeZone: "Asia/Shanghai",
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
hour12: false,
})
.format(new Date(iso))
.replace(/\//g, "-");
};
const grantText = (grants) => {
if (!Array.isArray(grants) || grants.length === 0) return "";
return grants
.map((grant) => {
const who = grant.recipient === "referrer" ? "Referrer" : "Invitee";
const type =
grant.grant_type === "rate_limit_reset_credit" ? "Reset credits" : grant.grant_type;
return `${who} +${grant.amount ?? 1} ${type}`;
})
.join(";");
};
const creditSourceText = (credit) => {
const title = credit?.title || "";
const description = credit?.description || "";
const profile = credit?.profile_user_id || "";
const text = `${title} ${description} ${profile}`.toLowerCase();
if (/codex team|thanks for using codex|official|官方/.test(text)) {
return "Official free grant";
}
return description || title || profile || credit?.reset_type || "Unknown source";
};
const creditUseTime = (credit) => {
if (credit?.redeemed_at) return formatTime(credit.redeemed_at);
if (credit?.redeem_started_at) return `Redeeming: ${formatTime(credit.redeem_started_at)}`;
return "";
};
const appendCell = (row, text, className = "") => {
const cell = document.createElement("td");
cell.textContent = text ?? "";
if (className) cell.className = className;
row.appendChild(cell);
return cell;
};
const renderTableMessage = (tbody, colspan, message, className = "empty") => {
tbody.replaceChildren();
const row = document.createElement("tr");
appendCell(row, message, className).colSpan = colspan;
tbody.appendChild(row);
};
const renderReferrals = (items) => {
referralsEl.replaceChildren();
if (!Array.isArray(items) || items.length === 0) {
renderTableMessage(referralsEl, 5, "No sent invitations yet");
return;
}
for (const item of items) {
const row = document.createElement("tr");
appendCell(row, item.email || "");
appendCell(row, item.status || "");
appendCell(row, formatTime(item.expires_at));
appendCell(row, grantText(item.grants));
const linkCell = document.createElement("td");
if (item.referral_link) {
const link = document.createElement("a");
link.href = item.referral_link;
link.target = "_blank";
link.rel = "noreferrer";
link.textContent = "Open";
linkCell.appendChild(link);
} else {
linkCell.textContent = "";
}
row.appendChild(linkCell);
referralsEl.appendChild(row);
}
};
const renderRewardCredits = (credits) => {
rewardCreditsEl.replaceChildren();
if (!Array.isArray(credits) || credits.length === 0) {
renderTableMessage(rewardCreditsEl, 5, "No reset credit rewards yet");
return;
}
for (const credit of credits) {
const row = document.createElement("tr");
appendCell(row, creditSourceText(credit));
appendCell(row, credit.status || "");
appendCell(row, formatTime(credit.granted_at));
appendCell(row, formatTime(credit.expires_at));
appendCell(row, creditUseTime(credit));
rewardCreditsEl.appendChild(row);
}
};
const parseResponse = async (response) => {
const contentType = response.headers.get("content-type") || "";
const text = await response.text();
if (contentType.includes("application/json")) {
try {
return text ? JSON.parse(text) : null;
} catch {
return { detail: "The server returned invalid JSON", raw: text.slice(0, 500) };
}
}
return {
detail: text
? `The server returned non-JSON content: ${text.slice(0, 300)}`
: "The server returned no content",
};
};
const readableValue = (value) => {
if (value === null || value === undefined || value === "") return "";
if (typeof value === "string") return value;
if (typeof value === "number" || typeof value === "boolean") return String(value);
if (Array.isArray(value)) return value.map(readableValue).filter(Boolean).join(", ");
const lines = [];
if (value.message) lines.push(String(value.message));
if (value.failed_emails?.length) {
lines.push(`Failed emails: ${value.failed_emails.join(", ")}`);
}
if (value.code) lines.push(`Error code: ${value.code}`);
if (value.error) {
const errorText = readableValue(value.error);
if (errorText) lines.push(`Error: ${errorText}`);
}
if (value.ineligible_reason) {
const reasonText = readableValue(value.ineligible_reason);
if (reasonText) lines.push(`Ineligibility reason: ${reasonText}`);
}
if (value.ineligible_reason_code) {
lines.push(`Ineligibility reason code: ${value.ineligible_reason_code}`);
}
if (value.detail && value.detail !== value) {
const detailText = readableValue(value.detail);
if (detailText) lines.push(detailText);
}
const unique = [...new Set(lines.filter(Boolean))];
return unique.length ? unique.join("\n") : JSON.stringify(value, null, 2);
};
const responseDetail = (data, fallback = "") =>
readableValue(data?.detail) ||
readableValue(data?.message) ||
(typeof data === "string" ? data : "") ||
fallback;
const responsePayload = (data) =>
data?.detail && typeof data.detail === "object" ? data.detail : data || {};
const responseMessage = (data, fallback = "") => {
const payload = responsePayload(data);
return payload.message || data?.message || responseDetail(data, fallback);
};
const failedEmails = (data) => {
const payload = responsePayload(data);
return Array.isArray(payload.failed_emails) ? payload.failed_emails : [];
};
const inviteResultTitle = (ok, message) => {
if (ok) return "Invitation sent successfully";
if (/cannot be referred/i.test(message)) return "This email cannot currently be invited";
return "Invitation was not sent successfully";
};
const api = async (path, options = {}, useAccount = true) => {
const response = await fetch(`${API_BASE}${path}`, {
...options,
credentials: "include",
headers: {
accept: "application/json",
authorization: `Bearer ${token}`,
...(useAccount && accountId ? { "chatgpt-account-id": accountId } : {}),
...(options.body ? { "content-type": "application/json" } : {}),
...(options.headers || {}),
},
});
return {
ok: response.ok,
status: response.status,
data: await parseResponse(response),
};
};
const getSession = async () => {
const response = await fetch("/api/auth/session", {
credentials: "include",
headers: { accept: "application/json" },
});
const session = await parseResponse(response);
token = session?.accessToken || session?.access_token || "";
if (!response.ok || !token) {
throw new Error("No Access Token in the browser session. Sign in on the current chatgpt.com page, refresh and try again.");
}
};
const selectAccount = async () => {
const result = await api("/wham/accounts/check", {}, false);
if (!result.ok) {
throw new Error(`Failed to load account: ${responseDetail(result.data, "Refresh the page and try again")}`.trim());
}
accountId =
result.data?.default_account_id ||
result.data?.account_ordering?.[0] ||
result.data?.accounts?.[0]?.id ||
"";
if (!accountId) {
throw new Error("The server did not return an available ChatGPT account ID.");
}
};
const refresh = async (message = "Refreshing the current browser account…") => {
setBusy(true);
setStatus(message);
try {
await getSession();
await selectAccount();
const [usage, rules, referrals, resetCredits, frontendEligibility] = await Promise.all([
api("/wham/usage"),
api(`/wham/referrals/eligibility_rules?referral_key=${encodeURIComponent(REFERRAL_KEY)}`),
api(`/wham/referrals?referral_key=${encodeURIComponent(REFERRAL_KEY)}`),
api("/wham/rate-limit-reset-credits"),
api(`/referrals/invite/eligibility?referral_key=${encodeURIComponent(REFERRAL_KEY)}`),
]);
const failures = [];
if (usage.ok) {
planEl.textContent = usage.data?.plan_type || "Unknown";
} else {
planEl.textContent = "Failed to load";
failures.push(`Plan: ${responseDetail(usage.data, "Try again later")}`);
}
const frame = rules.ok ? rules.data?.time_frame_rules?.[0] || {} : {};
const sent = Number(frame.invites_sent);
const total = Number(frame.invites_total);
const remaining =
Number.isFinite(sent) && Number.isFinite(total) ? Math.max(0, total - sent) : null;
if (rules.ok) {
inviteQuotaRemaining = remaining === null ? 0 : remaining;
quotaEl.textContent =
remaining === null ? "0 invitations" : `${remaining} invitations (used ${sent}/${total})`;
} else {
inviteQuotaRemaining = 0;
quotaEl.textContent = "0 invitations";
failures.push("Invitations remaining: quota endpoint unavailable; treated as 0");
}
if (resetCredits.ok) {
const availableCredits =
resetCredits.data?.available_count ??
usage.data?.rate_limit_reset_credits?.available_count ??
"Unknown";
const totalEarnedCredits = resetCredits.data?.total_earned_count;
creditsEl.textContent =
totalEarnedCredits === undefined
? `${availableCredits}`
: `Available ${availableCredits}, earned ${totalEarnedCredits}`;
renderRewardCredits(resetCredits.data?.credits || []);
} else {
const fallbackCredits = usage.ok
? usage.data?.rate_limit_reset_credits?.available_count
: undefined;
creditsEl.textContent =
fallbackCredits === undefined ? "Failed to load" : `Available ${fallbackCredits}`;
renderTableMessage(
rewardCreditsEl,
5,
`Failed to load reset credit rewards:${responseDetail(resetCredits.data, "Try again later")}`,
);
failures.push(`Reset credit rewards:${responseDetail(resetCredits.data, "Try again later")}`);
}
accountEl.textContent = accountId;
if (referrals.ok) {
renderReferrals(referrals.data?.items || []);
} else {
renderTableMessage(
referralsEl,
5,
`Failed to load invitation details:${responseDetail(referrals.data, "Try again later")}`,
);
failures.push(`Invitation details:${responseDetail(referrals.data, "Try again later")}`);
}
console.log("Codex invitation status: ", {
plan_type: usage.ok ? usage.data?.plan_type : null,
reset_cards_available: resetCredits.ok
? resetCredits.data?.available_count
: usage.data?.rate_limit_reset_credits?.available_count,
reset_cards_total_earned: resetCredits.ok ? resetCredits.data?.total_earned_count : null,
invites_sent: frame.invites_sent,
invites_total: frame.invites_total,
invite_remaining: remaining,
effective_invite_remaining: inviteQuotaRemaining,
time_frame: frame.time_frame,
account_id: accountId,
partial_failures: failures,
});
if (referrals.ok) {
console.table(
(referrals.data?.items || []).map((item, index) => ({
index: index + 1,
email: item.email,
status: item.status,
expires_at: formatTime(item.expires_at),
referral_id: item.referral_id,
grants: grantText(item.grants),
referral_link: item.referral_link,
})),
);
}
if (resetCredits.ok) {
console.table(
(resetCredits.data?.credits || []).map((credit, index) => ({
index: index + 1,
source: creditSourceText(credit),
status: credit.status,
granted_at: formatTime(credit.granted_at),
expires_at: formatTime(credit.expires_at),
used_at: creditUseTime(credit),
profile_user_id: credit.profile_user_id,
credit_id: credit.id,
})),
);
}
const eligibilityLine =
frontendEligibility.ok && frontendEligibility.data?.should_show !== false
? "Browser entry: currently visible"
: "Browser entry: currently hidden; the panel can still use endpoint results";
const successCount = [usage, rules, referrals, resetCredits].filter((r) => r.ok).length;
const statusTitle =
failures.length === 0
? "Status loaded."
: successCount > 0
? "Status partially loaded."
: "Failed to load status.";
const referralCountText = referrals.ok
? `${referrals.data?.items?.length ?? 0} records`
: "Unavailable";
const rewardCountText = resetCredits.ok
? `${resetCredits.data?.credits?.length ?? 0} records`
: "Unavailable";
setStatus(
[
statusTitle,
`Plan: ${planEl.textContent}`,
`Invitations remaining: ${quotaEl.textContent}`,
`Reset credits:${creditsEl.textContent}`,
`Invitation details:${referralCountText}`,
`Rewarddetails:${rewardCountText}`,
eligibilityLine,
...failures.map((failure) => `Not loaded: ${failure}`),
].join("\n"),
failures.length === 0 ? "ok" : successCount > 0 ? "warn" : "error",
);
} catch (error) {
token = "";
accountId = "";
inviteQuotaRemaining = 0;
planEl.textContent = "Failed to load";
quotaEl.textContent = "Failed to load";
creditsEl.textContent = "Failed to load";
accountEl.textContent = "Failed to load";
renderReferrals([]);
renderRewardCredits([]);
setStatus(error instanceof Error ? error.message : String(error), "error");
} finally {
setBusy(false);
}
};
const sendInvite = async () => {
if (busy) return;
const email = emailEl.value.trim();
if (!emailEl.checkValidity() || !email) {
emailEl.reportValidity();
return;
}
const confirmed = window.confirm(
`Send one Codex invitation using the current browser account?\n\nRecipient email: ${email}\nAccount ID:${accountId}`,
);
if (!confirmed) return;
setBusy(true);
setStatus(`Sending an invitation to ${email}…`);
try {
// Reload the browser session before sending to avoid using an expired token.
await getSession();
await selectAccount();
const result = await api("/wham/referrals/invite", {
method: "POST",
body: JSON.stringify({
referral_key: REFERRAL_KEY,
emails: [email],
}),
});
const invite =
result.data?.invites?.[0] ||
result.data?.items?.[0] ||
result.data?.referral ||
result.data ||
{};
const detail = responseMessage(
result.data,
result.ok ? "The server accepted the invitation request" : "The server rejected the invitation request",
);
const failed = failedEmails(result.data);
const resultTitle = inviteResultTitle(result.ok, detail);
const resultLines = result.ok
? [
invite.email ? `Email:${invite.email}` : "",
invite.referral_id ? `Referral ID:${invite.referral_id}` : "",
invite.invite_url || invite.referral_link
? `Invitation link: ${invite.invite_url || invite.referral_link}`
: "",
"Invitation quota and details refreshed automatically.",
]
: [
failed.length ? `Email:${failed.join(", ")}` : `Email:${email}`,
`Result: ${detail}`,
"The invitation was not sent; this email will not appear in the sent invitations list.",
];
const statusMessage = [resultTitle, ...resultLines.filter(Boolean)].join("\n");
setStatus(statusMessage, result.ok ? "ok" : "error");
console.log("Codex invitation response: ", {
http_status: result.status,
ok: result.ok,
response: result.data,
});
if (result.ok) {
await refresh("Sent successfully; reloading invitation quota…");
setStatus(statusMessage, "ok");
}
} catch (error) {
setStatus(error instanceof Error ? error.message : String(error), "error");
} finally {
setBusy(false);
}
};
$(".close").addEventListener("click", () => panel.remove());
refreshButton.addEventListener("click", () => refresh());
sendButton.addEventListener("click", sendInvite);
await refresh();
})();
Example:

Is this still live?
Community signal62% say it still works
