Skip to main content

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_at and 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 prefix and last_4 so 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_at is NULL.
  • expires_at is either NULL (never expires) or in the future.
  • expired_at is 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:

IntervalReminder milestones (days before expiry)
≤ 30 days7, 3, 1, 0
31–180 days30, 7, 3, 1, 0
> 180 days60, 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_expired 401 carrying a regenerate_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_keys row itself.
  • Every supplier_api_key_reminders idempotency 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.