◉CLAUDE LABJP
●2.1.301 — Adds the vim % motion, LaTeX math rendering in the VS Code panel, and a retry-watchdog limit for usage-limit waits (10/15)●2.1.299 — Conversations over 200K tokens left idle about 54 minutes get compacted; MCP allow rules now match only the server they name●SONNET 4.5 — Retires on the Claude API on 11/30, 45 days left. Migrate to Sonnet 5.5●WINDOWS — Reports say hooks break silently only on Japanese-locale Windows; check the character encoding first●FAST MODE — Opus 5.5 fast mode is out of research preview and no longer needs the beta header (10/15)●NEW — I Put a Ten-Line Promise File at the To…●2.1.301 — Adds the vim % motion, LaTeX math rendering in the VS Code panel, and a retry-watchdog limit for usage-limit waits (10/15)●2.1.299 — Conversations over 200K tokens left idle about 54 minutes get compacted; MCP allow rules now match only the server they name●SONNET 4.5 — Retires on the Claude API on 11/30, 45 days left. Migrate to Sonnet 5.5●WINDOWS — Reports say hooks break silently only on Japanese-locale Windows; check the character encoding first●FAST MODE — Opus 5.5 fast mode is out of research preview and no longer needs the beta header (10/15)●NEW — I Put a Ten-Line Promise File at the To…
Articles/API & SDK
⬡ API & SDK/2026-07-12Intermediate

When You Give an API Key an Expiration Date, Expiry Becomes a Plan Instead of an Accident

The Console now lets you set expiration dates on API keys. Here is how to fold planned expiry into unattended operations — with overlapping dual keys and a local expiry ledger — so your nightly jobs never go dark.

Claude API128API KeysAutomation45Security12Indie Dev23

One morning, a single one of my nightly jobs across several sites had stopped with a lone 401 in the log. The cause was mundane: months earlier I had tidied up a workspace, deleted an old key, and forgotten to swap it into one of the jobs. A key you delete by hand is hard to trace back later — which job used it, and when.

The Console now lets you set expiration dates on both API keys and Admin API keys. You can choose a preset, a custom date, or no expiry at all, and keys set to live longer than seven days get an email warning before they lapse. For an indie developer who leaves work to a model unattended, this quietly changes the relationship: instead of "a key might expire someday without warning," you get "this key expires on a date I know." The catch is that an expiring key, if you design around it carelessly, will stop your nightly job exactly on schedule. This article covers how to treat expiry as a plan rather than an accident.

Expiration turns "someday it lapses" into "it lapses on this date"

Until now, an API key lived until you or an admin explicitly deleted it. That sounds safe, but in practice it left the questions of "which key is old?" and "is a disused key sitting around unnoticed?" to human memory. An abandoned old key widens the damage if it ever leaks.

An expiration date inverts that relationship. The key lapses on the date you chose, and if you set it more than seven days out, you get an email as the deadline approaches. So instead of "it was already dead by the time I noticed," you get "I can see, seven days ahead, that it is about to lapse."

But there is a reality to unattended operation: relying on the notification alone is risky. Miss the email, and the job fails with a 401 the day the key expires. Handling expiry safely takes both a way to receive the warning and a way to swap the key before it lapses.

The fragile pattern is the "single key, one-shot swap"

The most breakable setup is holding a single production key, then, when the deadline arrives, creating a new one and deleting the old. That approach has a seam. In the few minutes between deleting the old key and reflecting the new one into your environment variables — or in any window where you forget to reflect it — the job fails to authenticate.

For unattended nightly runs, those few minutes of seam translate directly into "the whole night's work falls over." And the failure is silent; you do not notice until you read the log the next morning. Diagnosing the auth error itself is covered in How to Fix Claude API 401 Invalid API Key Authentication Error, but with expiration the focus shifts to not creating the seam in the first place.

Overlapping dual keys remove the seam

The basic move for removing the seam is to always hold two valid keys — one in active service, and one prepared as the next generation.

The steps go like this. When the active key (key-A) reaches seven days before expiry and the notification arrives, issue a new key (key-B) and give it an expiration date as well. Point your environment variable or secret store at key-B, run the nightly job once, and confirm it returns 200. Up to here key-A is still alive, so if the switch misbehaves you can fall back to it. Finally, watch key-B run for a day or two before revoking key-A.

This "overlap first, remove second" order is the crux. Delete the old key first and the whole point of dual keys evaporates. The scoped-rotation design itself is discussed in Stop the Bill Before It Balloons: Designing API Key Blast Radius for Unattended Pipelines. It helps to think of expiration as the mechanism that gives that rotation a deadline — an answer to "when do I do it?"

Don't wait for the Console email — let the job watch its own expiry

The notification email is useful, but a human receives it. Assuming the human will sometimes miss it, it pays to have the nightly job itself check how many days its key has left.

The approach is plain: when you issue a key, write its label and expiry date into a local ledger, and at the start of each nightly run, compute the days remaining. The Console API will not hand back a key's secret value later, but you can record the expiry date yourself.

import json
from datetime import date, datetime
from pathlib import Path
 
LEDGER = Path.home() / ".claude_key_ledger.json"
WARN_DAYS = 10  # margin so you notice before the Console's 7-day email
 
def check_key_expiry(active_label: str) -> None:
    """Call at the start of the nightly job. Warn when days remaining drop below the threshold."""
    if not LEDGER.exists():
        print("[key-check] No ledger found. Record expires_at when you issue a key.")
        return
 
    ledger = json.loads(LEDGER.read_text())
    entry = ledger.get(active_label)
    if not entry:
        print(f"[key-check] Label {active_label} is not registered in the ledger.")
        return
 
    expires = datetime.strptime(entry["expires_at"], "%Y-%m-%d").date()
    remaining = (expires - date.today()).days
 
    if remaining < 0:
        raise SystemExit(f"[key-check] {active_label} lapsed {abs(remaining)} days ago. Stopping.")
    if remaining <= WARN_DAYS:
        print(f"[key-check] Warning: {active_label} lapses in {remaining} days. Prepare the next key.")
    else:
        print(f"[key-check] {active_label} has {remaining} days left. All clear.")
 
# Example ledger (append when you issue a key)
# {
#   "prod-nightly-2026-07": {"expires_at": "2026-10-10", "note": "nightly across four sites"}
# }
 
if __name__ == "__main__":
    check_key_expiry("prod-nightly-2026-07")

Put this as the first step of the nightly job, and even if you miss the Console email, the log records the days remaining every night. The threshold (WARN_DAYS) sits ahead of the Console's seven-day notice so you keep a margin to act on your own side. If the key has already lapsed, the job stops rather than blindly generating a pile of 401s.

Which expiry length to choose — a per-use rule of thumb

Set the expiry length by the key's purpose and the effort of swapping it. Shorter shrinks the survival window if it leaks, but raises how often you overlap keys. Here is the rule of thumb I use.

PurposeSuggested expiryReasoning
Production nightly batch60-90 daysA quarterly overlap is manageable. Watch it twice, via the email and the ledger.
Testing / experiments7-30 daysThese tend to get abandoned. Keep them short so they clean themselves up.
CI / temporary automationAvoid "no expiry" if you canConsider dropping the key entirely and moving to keyless auth.
Local chat / drafting30-60 daysEven for personal use, build the habit of rotating on a schedule.

For CI and temporary automation, it is worth considering not holding a long-lived static key at all. The idea of leaning on short-lived credentials issued per request is covered in Drop Your Static Claude API Keys: Moving CI and Production to Keyless Auth with Workload Identity Federation. Expiration is the safety valve when you keep using static keys; keyless auth is the move that reduces the number of keys altogether — different roles.

Wrapping up — your next step

Expiration turns a key from "a seed of worry that might lapse someday" into "a managed thing with a known expiry date." But since a human receives the notification, unattended operation benefits from pairing the overlapping dual-key swap with a local expiry ledger, so your nightly jobs stay up.

As a first step, set an expiry on the key you use in production today, write that date into a ledger, and add the script above to the front of your nightly job. Next time you rotate, you can practice the overlap-before-remove order. It is a small piece of groundwork, but it cuts down on that quiet failure where a lone 401 is all that is left in the morning log.

Share

Thank You for Reading

Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

⬡ API & SDK2026-04-26
Building a Hard-to-Cancel Micro-SaaS with the Claude API — Six Implementation Patterns to Stabilize $1,000/Month
Hitting $1,000/month with a Claude-powered micro-SaaS is one challenge. Holding it for three months running is a much harder one. Here are six implementation patterns — with working Stripe and Cloudflare Workers code — that took my churn from above 10% to under 5%.
⬡ API & SDK2026-04-17
Building a GitHub PR Review Bot with Claude API — From Webhook Signature Verification to Structured Reviews with Tool Use
Build a production PR review bot with Claude API and GitHub Webhooks. Enforce structured scoring and security scanning via Tool Use, then handle the parts that bite in production — signature checks that pass only locally, 202 responses that hide failures, debouncing, and cost tracking.
⬡ API & SDK2026-10-16
Sending 40 Rows From Make to the Claude API Stops at Row 14 — Avoiding the 429 With One Subtraction
Why a no-code automation that sends spreadsheet rows to the Claude API one by one hits a 429 partway through, and how to fix it. Seven spacing strategies tested on a mock bucket, a formula for the Sleep delay, and settings that stop failed rows from vanishing.