Skip to content

fix: reconcile API key expiry docs with server-side 365-day cap - #407

Merged
mbevc1 merged 1 commit into
mainfrom
mintlify/api-key-expiry-reconcile
Sep 11, 2026
Merged

fix: reconcile API key expiry docs with server-side 365-day cap#407
mbevc1 merged 1 commit into
mainfrom
mintlify/api-key-expiry-reconcile

Conversation

@mintlify

@mintlify mintlify Bot commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

Addresses the two API key expiry issues raised in review.

Server-side confirmation

Verified in kosli-dev/server:

  • src/lib/constants.pyMAX_API_KEY_LIFETIME_DAYS = 365.
  • src/model/api_key.py::clamp_expires_at — omitting expires_at yields max_api_key_expires_at(); a later value is clamped to it. Enforced server-side, so every surface (CLI, Terraform, direct API) hits the same rule.
  • src/fastapi_app/models/service_accounts.py — request-model description already states this for both create (ApiKeyPostInput) and rotate (ApiKeyRotateInput, defaulting the new key to the rotated key's current expiry, capped by the maximum).

1. terraform-reference/resources/service_account_api_key.mdx

  • Dropped the # A non-expiring API key example comment.
  • Rewrote ## Expiry to state every key expires, the 365-day cap, and what happens when expires_at is omitted / within-cap / beyond-cap / in the past. Called out the Terraform-specific consequence of a longer expires_at (server clamps → inconsistent result after apply → resource marked tainted → next apply recreates).
  • Rewrote the Optional > expires_at schema description to match.
  • Updated the "null for a key that never expires" callout — that state can no longer exist.
  • Bumped the example dates from 2100-01-01 to 2026-12-31 so they fit inside the 365-day cap.

Upstream: kosli-dev/terraform-provider-kosli internal/provider/resource_service_account_api_key.go:76 already carries the correct schema description ("silently shortens a longer expiry", "keys that never expire can no longer be created"). The next agent sync of terraform-reference/ will overwrite this page with wording aligned to what I've just written, so no upstream change is needed.

2. Rotation docs

administration/authentication/api_key_rotation.md and tutorials/rotating_api_keys.mdx:

  • Added step 2 to the "How rotation works" list on both pages: the new key inherits the rotated key's expiry unless --expires-at / expires_at is passed, bounded by the 365-day cap.
  • Added a <Warning> on both pages spelling out the failure mode: a key rotated near end-of-life produces a replacement that dies at the same moment — the very failure rotation is supposed to prevent.
  • Tutorial CLI tab now shows --expires-at alongside --grace-period-hours, and the API tab example includes expires_at.

Not fixed here (out of scope)

client_reference/kosli_create_api-key.md and kosli_rotate_api-key.md still say "Defaults to no expiry". These are agent-synced from kosli-dev/cli (cmd/kosli/root.go:157-158 — where the flag descriptions already match v2.40.1: "Keys always expire: when not set, the key gets the maximum allowed lifetime..." and "Defaults to the rotated key's current expiry, and is capped by the maximum allowed key lifetime"). Next CLI reference sync will pick this up.

Validation

mint validate and mint broken-links clean on the changed pages.


Generated by Mintlify Agent.

Requested by: marko@kosli.com via Slack
Mintlify session: slack_1778705097.340389_D0ALT8RK1CJ

@mintlify
mintlify Bot requested a review from a team as a code owner September 11, 2026 15:14
@mintlify

mintlify Bot commented Sep 11, 2026

Copy link
Copy Markdown
Contributor Author

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated
kosli 🟢 Ready View Preview Sep 11, 2026, 3:15 PM

@claude

claude Bot commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

Claude finished @mintlify[bot]'s task in 3m 18s —— View job


Doc review — PR #407

Advisory review per the doc-review skill. 4 Improvements, 1 Suggestion, 1 upstream note. Three are posted inline; the rest are below.


Improvements

1. terraform-reference/resources/service_account_api_key.mdx:61 vs :96 — omitting expires_at means two different things

Line 61 says omitting it yields the 365-day maximum; line 96 says removing a previously set value leaves the existing expiry unchanged. Both are true, for creation and for an existing resource respectively, but the page no longer distinguishes them — the clause that used to explain the follow-up (to get a non-expiring key again, the key must be recreated) was dropped in this PR. A reader who deletes expires_at from a live resource expecting a fresh 365 days gets no change, and the page now documents no way to mint a replacement. Details and a suggested rewrite inline.

2. .../service_account_api_key.mdx:63 — verify the tainted claim

marks the resource tainted so the next apply revokes and recreates it is a Terraform-mechanics assertion I can't check from this repo. Provider produced inconsistent result after apply is an error diagnostic; whether the object is tainted and auto-recreated depends on the operation and Terraform version, and terraform taint has been deprecated in favour of -replace= since 0.15.2. Confirm against a real apply of internal/provider/resource_service_account_api_key.go, or state only the verifiable part. Folded into the inline suggestion above.

3. tutorials/rotating_api_keys.mdx:30 and administration/authentication/api_key_rotation.md:21 — the Warning is unactionable for web-UI readers

Both Warnings offer --expires-at as the only remedy, but the Web UI tab (rotating_api_keys.mdx:38-45) is the first tab and exposes only a grace period. administration/authentication/service_accounts.md:24 shows the create dialog has a TTL field — if Regenerate does too, document it in Web UI step 5; if not, say plainly that UI rotation always inherits the old expiry. Inline.

4. Incomplete sweep — the key-creation pages still say nothing about the cap

  • administration/authentication/service_accounts.md:24 — "Choose a Time-To-Live (TTL)"
  • user/personal_api_keys.md:23 — same wording

These are where a reader actually picks an expiry, and neither mentions that TTL is capped or that no-expiry is gone. The PR reconciled the Terraform and rotation surfaces but not the two UI creation surfaces. Confirm first whether MAX_API_KEY_LIFETIME_DAYS applies to personal keys as well as service-account keys before stating 365 days on personal_api_keys.md. Fix this →


Suggestion

tutorials/rotating_api_keys.mdx:101 — the automation example drops expires_at

The "capture directly into your secrets store" example still sends {"grace_period_hours": 24}. That's the snippet people will paste into automation, and automated rotation is exactly the case the new Warning describes. Either add expires_at here too or note that it inherits.

Related, and also posted inline: the new example dates are all near-term and fixed — --expires-at 2026-12-31 (line 54), expires_at: 1798761600 = 2027-01-01 (line 89), 2026-12-31T00:00:00Z (terraform page lines 45, 59, 96), and timeadd("2026-01-01T00:00:00Z", "8760h") (line 68, which resolves to 2027-01-01 and so goes stale despite looking dynamic). Since the pages state past values are rejected server-side, these become examples that error when copied after those dates. <<YYYY-MM-DD>> matches the tutorial's existing placeholder convention. Inline.


Upstream — does not block this merge

client_reference/kosli_create_api-key.md:21 and client_reference/kosli_rotate_api-key.md:23 both still say Defaults to no expiry, which now directly contradicts this PR's prose on the live site until the next CLI reference sync. The PR body correctly identifies the source as kosli-dev/clicmd/kosli/root.go:157-158, already fixed there.

One thing the PR body doesn't cover: the kosli rotate api-key long description (kosli_rotate_api-key.md:14-17) is a separate Go string and says nothing about expiry at all — it describes only the grace period. If the goal is that every surface states the inheritance rule, that string needs updating too, in kosli-dev/clicmd/kosli/rotateApiKey.go.


3 Improvements inline, 1 Improvement + 1 Suggestion above, 1 upstream note. Nothing Critical: no new pages, no moves, no relative links, and every internal target in the changed files resolves (checked by hand — link-rot returned NEUTRAL, so it validated nothing). Merge once #1 and #3 are resolved; the rest are non-blocking.
· mintlify/api-key-expiry-reconcile

@mbevc1 mbevc1 changed the title Reconcile API key expiry docs with server-side 365-day cap fix: reconcile API key expiry docs with server-side 365-day cap Sep 11, 2026
@mbevc1
mbevc1 merged commit eb4485e into main Sep 11, 2026
7 of 8 checks passed
@mbevc1
mbevc1 deleted the mintlify/api-key-expiry-reconcile branch September 11, 2026 15:17
Comment on lines +61 to +66
- **Omitted** → the server applies the maximum 365-day expiry.
- **Set within 365 days** → honored as-is.
- **Set beyond 365 days** → silently shortened to the 365-day cap. Terraform then reports the result as an inconsistent result after apply and marks the resource tainted so the next apply revokes and recreates it. Choose an `expires_at` within the 365-day window to avoid this.
- **In the past** → rejected server-side at apply time.

Keys that never expire can no longer be created.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "Omitted → the server applies the maximum 365-day expiry" bullet is true only at creation, and it now reads as contradicting the schema entry at line 96 ("Removing a previously set value from configuration leaves the existing expiry unchanged").

The old text closed that gap with the trailing clause you removed (to get a non-expiring key again, the key must be recreated (e.g. via terraform taint ...)). Without it, a reader who deletes expires_at from a live resource to "reset the clock to 365 days" gets no change at all, and the page doesn't say how to actually get a fresh key.

Suggested change
- **Omitted** → the server applies the maximum 365-day expiry.
- **Set within 365 days** → honored as-is.
- **Set beyond 365 days** → silently shortened to the 365-day cap. Terraform then reports the result as an inconsistent result after apply and marks the resource tainted so the next apply revokes and recreates it. Choose an `expires_at` within the 365-day window to avoid this.
- **In the past** → rejected server-side at apply time.
Keys that never expire can no longer be created.
- **Omitted at creation** → the server applies the maximum 365-day expiry.
- **Set within 365 days** → honored as-is.
- **Set beyond 365 days** → silently shortened to the 365-day cap. Terraform then reports `Provider produced inconsistent result after apply`, and the key you get does not have the expiry you asked for. Choose an `expires_at` within the 365-day window to avoid this.
- **In the past** → rejected server-side at apply time.
Keys that never expire can no longer be created. Removing `expires_at` from an existing resource does not extend the key — the expiry set at creation stands. To mint a replacement with a fresh 365 days, force a new key with `terraform apply -replace=kosli_service_account_api_key.ci_key`.

Two things folded into the suggestion, take or leave independently:

  • marks the resource tainted so the next apply revokes and recreates it is a specific Terraform-mechanics claim I can't verify from this repo. Provider produced inconsistent result after apply is an error diagnostic; whether the object ends up tainted and auto-recreated depends on the operation and Terraform version, and terraform taint has been deprecated in favour of -replace= since 0.15.2. Either confirm it against a real apply of internal/provider/resource_service_account_api_key.go or state only the part that's certain.
  • -replace= as the documented remedy, since the page no longer mentions any way to get a fresh key.

Comment on lines +29 to +31
<Warning>
Rotation on its own does not extend the credential. If the rotated key is already close to its expiry (for example, most of the way through the 365-day cap), the new key inherits that expiry and dies at the same moment — the exact failure rotation is supposed to prevent. Pass `--expires-at` to reset the clock, up to the 365-day cap.
</Warning>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Warning's only remedy is --expires-at, but the Web UI tab (lines 38–45) is the first tab and offers only a grace period — no expiry field. So the reader most likely to hit this failure mode is the one given no way to fix it.

administration/authentication/service_accounts.md:24 shows the create dialog does have a TTL field. If Regenerate has one too, document it in step 5 of the Web UI tab and reference it here. If it doesn't, say so explicitly, e.g.:

…Pass --expires-at (CLI) or expires_at (API) to reset the clock, up to the 365-day cap. Rotating in the web app always inherits the old key's expiry — use the CLI or API when you need to extend it.

Same applies to the Warning at administration/authentication/api_key_rotation.md:20-22, which names only the CLI flag.

kosli rotate api-key <<key-id>> \
--service-account <<service-account-name>> \
--grace-period-hours 24 \
--expires-at 2026-12-31 \

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2026-12-31 is 111 days out from today, and the docs state past values are rejected server-side. From 2027-01-01 this becomes an example that errors when copied. The old 2100-01-01 violated the cap but never went stale; swapping in a fixed near-term date trades one wrong example for one with an expiry date of its own.

This page already uses the <<...>> placeholder convention for <<key-id>> and <<service-account-name>>, so:

Suggested change
--expires-at 2026-12-31 \
--expires-at <<YYYY-MM-DD>> \

Same issue at line 89 — 1798761600 is 2027-01-01 — and in terraform-reference/resources/service_account_api_key.mdx at lines 45, 59 and 96 (2026-12-31T00:00:00Z), plus line 68's timeadd("2026-01-01T00:00:00Z", "8760h"), which resolves to 2027-01-01 and so goes stale on the same date despite looking dynamic.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant