Key lifecycle
A partner API key passes through five states from birth to grave:
Each transition has a specific trigger. None is implicit, except for the
two scheduled ones (auto-disable at expiry, hard-delete at expiry + 30
days), which are driven by Nuntiq's supplier_api_maintenance
scheduler task.
Invited: a row, no key yet
When a customer triggers your invitation, Nuntiq creates a
supplier_api_regen_sessions row holding a hashed magic token and binds
it to the partner account. The token itself is in the link in the email,
not in any DB row. There is no api_key yet.
This row is single-use and TTL'd:
- 15 minutes from issue to claim.
- 5 wrong code attempts locks the session.
- One successful claim marks it
used_atand it cannot be replayed.
Session rows that stay unused are swept after 7 days by the maintenance task.
Claim: born once, shown once
The partner clicks the magic link → lands on the supplier portal regen
page → asks for the 6-digit code → enters it → picks a label and an
expiration interval (1 month / 3 months default / 6 months / 1 year /
Never) → the portal calls POST /partner/supplier-access/mint →
Nuntiq mints the credentials and returns them once.
The mint endpoint stores:
- The HMAC-with-pepper hash of the
api_key. - The HMAC-with-pepper hash of the
rotation_secret. - The chosen
expires_interval_days(so future rotations can default to the same interval). - A computed
expires_at(NOW + interval, or NULL for "never"). - The visible
prefixandlast_4so you can recognize the key in later listings.
Plaintext is never persisted server side and never re-shown. We cannot look it up for you, no support engineer can either — the recovery path is "generate a new one through the same flow."
A partner_key_issued email fires to every notification address on the
account. It does not contain the key, only that one was just issued
and when it expires.
Active: the working state
While a key is active:
revoked_atis NULL.expires_atis either NULL (never expires) or in the future.expired_atis NULL (the maintenance task has not stamped it yet).
The auth path accepts the key, every Partner API call succeeds,
last_used_at is debounced-updated about once per minute.
Reminders during the active window
The supplier_api_maintenance task runs daily on the global Nuntiq
scheduler. For every key with expires_at IS NOT NULL, it considers a
tiered set of milestones based on the interval:
| Interval | Reminder milestones (days before expiry) |
|---|---|
| ≤ 30 days | 7, 3, 1, 0 |
| 31–180 days | 30, 7, 3, 1, 0 |
| > 180 days | 60, 30, 7, 3, 1, 0 |
| Never | — |
On each run, it fires only the most-urgent un-sent open milestone per key, so a missed day does not trigger a flood. It also marks every less-urgent open milestone as superseded with an empty recipient list, which means a key already past its expiry will never later emit a stale "expires in 7 days" email.
Each reminder email respects per-recipient opt-outs; see Email preferences.
Self-rotate: stays Active, just gets a fresh pair
Your backend can rotate its own key in place at any time:
POST /v1/partner/account/keys/{key_id}/rotate
X-API-Key: <current key, must match :key_id>
X-Rotation-Secret: <current rotation secret>
You get a new api_key + new rotation_secret back. The same key row
stays alive with its id, label, etc. unchanged. The previous
api_key keeps working for 4 hours via the grace window so you can
roll your process restart without an outage window.
Full mechanics: Rotation.
Expired: auto-disabled at the expiry timestamp
When expires_at passes:
- Auth lookups stop accepting the key (the SQL WHERE clause excludes
expires_at < NOW()). - The daily maintenance task stamps
expired_at = NOW()as an explicit "this key is disabled because it expired" flag, distinct from a manual revoke. - A day-0 "key expired" reminder email is sent (if the recipient has
not opted out of
reminder_expired). - Requests using the key now get a structured
key_expired401 carrying aregenerate_url.
The key sits in this state for 30 days. During that window it remains recoverable in audit and reporting; nothing about the row is destroyed except its ability to authenticate.
Hard-deleted: 30 days after expiry
After expiry + the retention window (default 30 days), the maintenance
task DELETEs the row. Two things vanish with it:
- The
supplier_api_user_keysrow itself. - Every
supplier_api_key_remindersidempotency row for this key, cascaded by the foreign key.
Audit log entries in the customer DB are kept according to that customer's own retention policy and are not cascaded.
Revoked: customer or admin kills the key
The customer can revoke a key from their admin or customer portal at any
time. That sets revoked_at = NOW() and revoked_reason = '<their note>'. The key stops authenticating immediately.
You cannot revoke the key you are currently authenticating with through the API. To "rotate someone else's key", DELETE + POST a new one, as two calls.
Revoked keys follow the same 30-day hard-delete retention.