The key_expired error
When you send a request with an expired or expired-disabled key, Nuntiq deliberately returns more information than a generic 401. This page documents the response and the recovery path.
The motivation is straightforward: if a partner's recipients muted every reminder email, the only signal a key is gone is the next request breaking. This response tells your integration log exactly where to send a human to get it back.
What it looks like
HTTP/1.1 401 Unauthorized
Content-Type: application/json
{
"error": "key_expired",
"message": "This API key expired on 2026-08-18. Generate a new key at https://acme.dev.apquery.com/supplier-access/regenerate",
"regenerate_url": "https://acme.dev.apquery.com/supplier-access/regenerate"
}
Three fields beyond the standard message:
| Field | Meaning |
|---|---|
error | Always "key_expired". Use this to branch on the error type without parsing the message. |
message | Human-readable, includes the expiry date and the regenerate URL. |
regenerate_url | Per-tenant URL of the supplier portal regen page. Send a human here. |
When you'll see it
Either of:
expires_aton the key has passed.- The daily
supplier_api_maintenancetask has stampedexpired_aton the key (which it does for every key whoseexpires_atis in the past).
Both conditions resolve to the same response.
What it doesn't tell you
- Which key (no
key_id, nolast_4). The 401 is generated by the auth path, which deliberately does not enumerate keys. - Whether the key was revoked. A revoked key returns the standard
"Invalid API Key"401, not this one. (Revocation is a manual kill; expiry is a scheduled lapse; they have separate audit trails.) - Whether your
rotation_secretis still valid. It is not — the same hard-delete that removes the key row also removes the rotation secret. You cannot rotate an expired key. You can only regenerate.
Recovery
- Log loudly. Your integration's error log should make this response visible — it is the signal that everything stops working in less than 30 days unless someone acts.
- Send a human to
regenerate_url. They follow the same claim flow as at first issue: enter email, request 6-digit code, enter code, pick a new expiration interval, claim. Bothapi_keyandrotation_secretare issued fresh. - Replace your stored credentials. The old
key_idis gone (the regen flow always mints a new row). Update your vault, restart your process, retry.
There is no programmatic recovery path. By design — an expired key means the partner organization needs to make a deliberate choice about who is responsible for the new one.
Suggested client-side handling
def call_partner_api(method, path, **kwargs):
resp = requests.request(method, NUNTIQ_BASE + path, **kwargs)
if resp.status_code == 401:
body = resp.json() if resp.headers.get("content-type", "").startswith("application/json") else {}
if body.get("error") == "key_expired":
log.error(
"Nuntiq partner key expired. Regenerate at %s. Message: %s",
body.get("regenerate_url"),
body.get("message"),
)
# Page the on-call human, do not retry.
raise NuntiqKeyExpired(body.get("regenerate_url"), body.get("message"))
resp.raise_for_status()
return resp.json()
The pageable NuntiqKeyExpired exception is the point: this is
not a transient failure. Exponential backoff will burn CPU forever. A
human has to claim a new key.
Related
- Key lifecycle: the full state machine.
- Rotation: how to avoid getting here.
- Email preferences: so a human notices before it lapses.