OpenAI API Key Expiration: expires_in_seconds, Org Policies, and Safe Automation
Service Account API key expiry got its key upgrade in v3.11.0 / v7.13.0: org and project policies can now force expiration with a hard maximum lifetime. Parameter boundaries, code, and an ops checklist, all in one place.
How to
Upgrade to an SDK with policy semantics
Upgrade to openai>=3.11.0 (Python) or openai (openai-node)>=7.13.0. The basic expiration fields exist since v3.10.0 / v7.11.0; the policy-constrained semantics take effect in v3.11.0 / v7.13.0.
Pass expires_in_seconds when creating the service account
Call admin.organization.projects.serviceAccounts.create (or your client's equivalent) with name and expires_in_seconds (an integer between 1 and 31536000). If your organization or project has a maximum-lifetime policy, the value must not exceed it.
Record expires_at from the response
The response's expires_at is a Unix timestamp in seconds; null means the key does not expire. Put it in your key inventory and schedule rotation before the deadline.
Handle rejected values
Zero or negative values, values above 31536000, values above the policy limit, and combinations with create_service_account_only=true are rejected by schema validation — catch the 400 and surface the reason to the caller.
The expiry semantics of Service Account API keys graduated from "an optional field" to "policy governance" on 2026-09-09. openai-python v3.11.0 and openai-node v7.13.0 (both published around 15:30 UTC that day; release notes headlined "Add expiration controls for service account keys" / "Add API key expiration controls") gave expires_in_seconds hard bounds and organization-level policy constraints — "this key never expires" is no longer the final state of the default; it is now the temporary state of "no policy requires you to expire yet."
Every fact in this article comes from the two packages' release notes, PR diffs, and the v3.11.0 tag source re-fetched on 2026-09-10 (references at the end). OpenAI's documentation domains returned 403 to this site that day, so statements about console-side operations stay within what the type layer shows.
1. Overview: what changed
The OpenAI API key expiration policy is the Service Account key expiry semantics as of 2026-09-09: create a service account with expires_in_seconds (1 to 31536000 seconds) to bound the initial key's lifetime, and an organization or project policy can require expiration with a maximum lifetime.
The timeline has two steps that are easy to conflate:
| Version | Published (UTC) | Capability delivered |
|---|---|---|
| openai-python v3.10.0 / openai-node v7.11.0 | 2026-09-08 / 09-08 | Basic expiration fields: expires_in_seconds (request) + expires_in_seconds / expires_at (response) |
| openai-python v3.11.0 / openai-node v7.13.0 | 2026-09-09 15:30 | Policy-constrained semantics: org/project policies can force expiration; bounds validation (1..31536000); the create_service_account_only conflict |
Step one was "you can pass this field." Step two is "your organization can force you to pass it — and cap how large it can be." The two packages' version numbers do not map one-to-one, which is normal for OpenAI's dual SDKs.
2. The full semantics of expires_in_seconds
The docstring in v3.11.0's ServiceAccountCreateParams (src/openai/types/admin/organization/projects/service_account_create_params.py) deserves to be read in full:
Number of seconds until the initial API key expires. If omitted or null, the key does not expire unless the effective organization or project policy requires an expiration. When a policy sets a maximum lifetime, this value must be provided and must not exceed that limit. A non-null value cannot be used when
create_service_account_onlyis true.
Broken into four rules:
- Explicit value: the initial key expires after
expires_in_secondsseconds; valid range is 1 to 31536000 (365 days) — schema-enforced (minimum: 1/maximum: 31536000), anything outside is rejected. - Omitted or null: the key does not expire — but only when no effective organization/project policy requires expiration. With a policy in place, the key expires per policy even though you passed nothing.
- Policy with a maximum lifetime:
expires_in_secondsflips from optional to mandatory, and the value must not exceed the policy limit. - Mutually exclusive with create_service_account_only:
create_service_account_only: truecreates a service account with no initial key — there is no initial key to expire, so a non-null value is rejected.
One-sentence summary: the policy is the new highest priority. Whether or not your code passes a value, it first has to answer "does my organization have a policy, and what is its cap?"
3. The org/project policy: what the type layer confirms and what it does not
Confirmed (type-layer evidence): the policy's effects — it can expire keys created with no explicit value, cap expires_in_seconds, and turn the optional into the mandatory.
Not confirmed (no type-layer evidence, as verified on 2026-09-10): the policy's management endpoints. Neither SDK ships resources or methods for creating, querying, or modifying policies. Based on the available evidence, policies are set through the OpenAI admin console; the types show no counter-evidence and no evidence of an API. When an endpoint appears in the type layer, this site will follow up.
For multi-team organizations this is a classic platform-governance signal: a security team can mandate "every service account key lives at most N days" at the org level, and application code gets constrained without changing a line — over-limit values are rejected, and value-less keys expire per policy.
4. Code
4.1 Creating a Service Account with an expiring key (Python)
from openai import OpenAI
client = OpenAI()
sa = client.admin.organization.projects.service_accounts.create(
project_id="prj_xxx", # placeholder
name="ci-runner",
expires_in_seconds=60 * 60 * 24 * 90, # 90 days (7,776,000 s; valid range 1..31536000)
)
print(sa.expires_at) # Unix timestamp (seconds); null = never expires
4.2 The same call in Node
import OpenAI from 'openai';
const client = new OpenAI();
const sa = await client.admin.organization.projects.serviceAccounts.create({
project_id: 'prj_xxx', // placeholder
name: 'ci-runner',
expires_in_seconds: 60 * 60 * 24 * 90,
});
console.log(sa.expires_at); // Unix timestamp (seconds); null = never expires
4.3 Auditing existing keys' expiration (Python)
keys = client.admin.organization.projects.api_keys.list(project_id="prj_xxx")
for k in keys.data:
print(k.id, k.expires_at) # project API key objects carry expires_at as of v3.11.0
All three snippets are SDK-source-verified (field-by-field against the v3.11.0 type definitions) and were not executed against a live key; replace placeholder IDs such as project_id with your own resources.
5. Ops checklist: putting policy semantics to work
- CI and automation: align key lifetime with deployment cadence — pass 30 days (2,592,000 seconds) for pipeline keys with a rotation calendar. Do not treat the 365-day cap as a target; it is a ceiling.
- Key inventory: record every key's expires_at and monitor deadlines. Legacy keys with null expires_at will not expire until your organization sets a policy — and their behavior will change when you do, so inventory first.
- Error handling: zero, negative, above 31536000, above the policy limit, or combined with
create_service_account_only: trueare all schema-rejected. Catch the 400-class errors and surface the reason instead of letting it become a 3 a.m. page. - Upgrade order: upgrade the SDK (Python ≥ v3.11.0 / Node ≥ v7.13.0) before touching key strategy — older types lack the policy semantics, and validation error messages will not line up.
6. Common pitfalls
- Passing 0 or a negative number: the schema requires minimum: 1 and rejects it outright. "Expire immediately" is not what this parameter does — it sets lifetime at creation, it is not a revocation tool.
- Exceeding 31536000: 365 days is a hard ceiling. "Expire in two years" gets rejected; staged rotation is the right answer.
- Combining with create_service_account_only: mutually exclusive. Omit expires_in_seconds when creating a key-less service account.
- Assuming this covers user keys: as verified on 2026-09-10, expiration fields exist only on Service Account keys and the project API key object; the type layer shows no corresponding change for user API keys.
- Policy-limit errors that do not match the docs: the policy limit is organization-specific and invisible in the SDK types — the cap in an error message reflects your organization's configuration, which is exactly why your code must be able to read policy from the error.
7. Next steps
- openai-python 3.9/3.10 and openai-node 7.11/7.12: Prompt Cache Diagnostics, API Key Expiry, GPT Image 2.5: where the basic expiration fields first landed (v3.10.0 / v7.11.0), plus the rest of that batch.
- gpt-6-astra appears in the OpenAI SDK: the new ChatModel ID and the Safety Alerts API: the other major type-layer shift in the SDKs that same week.
- OpenAI API Error Handling and Retry: 401/429/5xx Patterns: where schema-rejection (400) sits relative to rate limiting (429) on the retry spectrum.
- Codex CLI in GitHub Actions: production-grade CI integration: the full scenario behind this article's "30-day CI keys" checklist item.
Key points
- Valid range for expires_in_seconds: 1 to 31536000 seconds (365 days), enforced by schema validation, since 2026-09-09 (openai-python v3.11.0 / openai-node v7.13.0)
- The default changed in meaning: omitted or null = the key does not expire — but only when no organization/project policy requires expiration; with a policy in place, keys expire per policy
- When a policy sets a maximum lifetime: expires_in_seconds becomes mandatory and must not exceed the policy limit
- When create_service_account_only is true (creating a service account without an initial key), a non-null expires_in_seconds is rejected
- Response side: both the project API key object and the service account creation response carry expires_at (Unix timestamp in seconds; null = never expires)
- As verified on 2026-09-10: no endpoint for creating or querying policies appears in either SDK's types — per the type-layer evidence, policies are set through the admin console
Frequently asked questions
Official references
- Changelogopenai-python v3.11.0 Release Notes (GitHub)
- Changelogopenai-node v7.13.0 Release Notes (GitHub)
- Docsopenai-python PR #3825: Add expiration controls for service account keys
- Docsopenai-python v3.11.0 service_account_create_params.py (expiration parameter source)
- Docsopenai-python v3.11.0 project_api_key.py (expires_at field source)
Related articles
OpenAI API 429 Rate Limit Errors: RateLimitError and SDK Retries Explained
A 429 is two problems in one status: throughput limits vs quota exhaustion. The Python SDK already retries twice and honors Retry-After — this guide explains the mechanics from source.
Read articleopenai-node v7.20.0 Explained: Environment-Variable Vault Credentials, External Storage, and Safety Cases
Six PRs in one openai-node release: environment_variable vault credentials, external storage management, safety case retrieval with two webhook events, a SIP media security field, and a legacy GET fix — each traced to PR and tag sources.
Read articleopenai-python 3.15/3.16 and openai-node 7.18/7.19: Cache Prewarming, Webhook Management, connector_id Deprecation
Six OpenAI SDK releases in one day: prewarm cache warming, client.webhooks endpoint management, connector_id deprecated for post-September-1 models, WebSocket sessions in both languages. Every item traced to its PR.
Read articleSubscribe to GPTMap Weekly
One email every Monday: curated OpenAI updates, deep dives, and best practices. No ads, unsubscribe anytime.
Submitting opens Buttondown in a new tab to confirm your subscription.