/new-api-version — stand up a new apiVersion tier (V3, V4, …)
SkillDev toolsGuided workflow for standing up a NEW Jellyfin apiVersion tier (V3, V4, …) in JellyRock when an upstream release restructures the API enough to need a new `if m.getApiVersion() >= N` dispatch level. Wraps the "Adding Support for New Server Versions" recipe in docs/dev/jellyfin-server-versioning.md and walks every surface that must move together — the boundary map (YAML), its BrightScript twin `resolveApiVersion()`, the `sdkVN.bs` endpoints, the dispatch branches, the device profile, the manifest tier-clamp, and the validators — stopping at each step so the tier split can't land half-built. Use when proactive RC/master triage (via `/server-upgrade <rc-or-unstable>`) shows a breaking API shift that a per-method `>= N` branch must cover. NOT for routine endpoint additions inside an existing tier (those use the existing `>= 2` shape directly).
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the /new-api-version — stand up a new apiVersion tier (V3, V4, …) skill
What this skill tells your AI
The instructions your AI receives, as published by jellyrock/jellyrock in .claude/skills/new-api-version/SKILL.md and read by ahel’s review.
JellyRock dispatches Jellyfin endpoints by integer apiVersion tier with if m.getApiVersion() >= N (V1 = server 10.7–10.8, V2 = 10.9+). When an upstream release restructures the API enough that a per-method branch needs a new level, you stand up the next tier. This skill wraps the canonical recipe — docs/dev/jellyfin-server-versioning.md → "Adding Support for New Server Versions" — and walks each surface in order so a contributor can't land a half-built tier. Read that section first; the steps below sequence it and add the verify gates.
The load-bearing invariant: three twins must agree. docs/dev/jellyfin-version-boundaries.yml (what the server-upgrade tooling reads), source/utils/misc.bs resolveApiVersion() (what the app runs to pick the tier), and the sdkVN.bs filename convention the manifest generator keys off. If they disagree, the dispatch is dead code or the tooling's tier-relevance analysis lies. Every step keeps them in lockstep.
Step 0 — Confirm a new tier is actually warranted
A new tier is justified only when an endpoint JellyRock uses changes shape in a way a single new >= N branch must guard (moved path, restructured request/response, a param that moved) — the V1→V2 kind of shift, not a one-off field tweak. Additive fields are handled with isValid()/?? in place; a routine new endpoint uses the existing top tier's >= 2 shape directly (see source/api/CLAUDE.md) — stop and use that if that's all this is. Ask the user to name the concrete breaking change(s) and the server version X that introduces them (usually surfaced by /server-upgrade <rc-or-unstable>'s proactive triage). Do not proceed without X.
Step 1 — Boundary map + its BrightScript twin (do these together)
These are the two halves of the same fact; they ship in one change set.
-
Boundary map — in
docs/dev/jellyfin-version-boundaries.yml: the next tierN= (highest existing key) + 1. Flip the currently-activetier tofrozenwith a concretemaxServer(last release beforeX); add tierNasactive,minServer: X,maxServer: null. Propose the diff and wait for confirmation (two systems read this file). The loader enforces exactly oneactivetier and it must be the unbounded one, so flip-and-add is atomic. Example V3 at10.12.0:tiers: 2: { minServer: '10.9.0', maxServer: '10.11.10', status: frozen } # ← was active/null 3: { minServer: '10.12.0', maxServer: null, status: active } # ← new -
resolveApiVersion()twin — insource/utils/misc.bs, add the new tier's check above the existing ones (highest version wins), mirroring the YAMLminServer, and update the header comment:if versionChecker(serverVersion, "10.12.0") ' ← V3 min, matches boundaries.yml tier 3 return 3 end if if versionChecker(serverVersion, "10.9.0") return 2 end if return 1
Step 2 — Add the sdkVN.bs user-endpoint shim + dispatch branches
The user-scoped endpoints that moved are implemented in version-named shim files (source/api/sdkV1.bs, source/api/sdkV2.bs) — the manifest generator's tier-clamp keys off these filenames (Step 4), so the convention is load-bearing, not cosmetic.
- Create
source/api/sdkV<N>.bswith the new endpoint shapes (model it onsdkV2.bs). - Add the
if m.getApiVersion() >= Ndispatch branches insource/api/ApiClient.bs— keep the existing>= 2shape, put the new>= Nbranch ahead of it.grep -rn "getApiVersion" source/surfaces every dispatch site (ApiClient.bs,sdk.bs,items.bs,deviceCapabilities.bs) so none is missed. - The endpoint-specific request/response logic is yours to fill from the diff — this skill scaffolds the file + branches and points at the sites; it does not invent the new API shape. Leave a clear
TODO(V<N>)marker on any branch you stub before the spec finalizes (RCs can still change — that's why/server-upgradere-diffs).
Step 3 — Device profile branch
If the new tier changes the device-profile shape, add a V<N> branch in source/utils/deviceCapabilities.bs (the V1/V2 selectors are already internal; V<N> follows the same getApiVersionFromGlobal()-keyed pattern). Skip if the profile is unchanged.
Step 4 — Manifest tier-clamp + regenerate
The API-usage manifest (docs/architecture/api-usage-manifest.json) is AST-generated and drift-gated. Its cross-function clamp (scripts/generate/api-usage-manifest.js, ~line 467) pins endpoints by shim filename. Add the new tier and clamp the now-frozen middle tier:
sdkV<N>.bs→minApiVersion = Math.max(min, N)- the previously-active shim (e.g.
sdkV2.bs) → gains amaxApiVersion = Math.min(max ?? N-1, N-1)clamp (it's now the frozen middle tier)
Then regenerate + commit:
npm run docs:api-manifest # re-extracts; the >= N branches from Step 2 are picked up automatically
Step 5 — Endpoint-availability ledger (if relevant)
If the new tier introduces endpoints reached on older servers via a guard/sibling, register them in docs/dev/jellyfin-endpoint-availability.yml (see its header for the schema) so the floor-coverage check doesn't re-flag them every release, then validate:
npm run lint:endpoint-availability # checks each entry's guard/sibling claim against current code
Step 6 — Verify statically (no Roku hardware needed)
Standing up a tier is verified entirely by static analysis of the .bs sources — the same kind of AST scan CI already runs over the API files — so you never have to sideload to a device to confirm the wiring:
- Twin-consistency gate (the load-bearing check) —
npm run lint:apiversion-consistencyparsesresolveApiVersion()frommisc.bswith the BrighterScript AST and asserts itsversionChecker(serverVersion, "X.Y.Z") → Nguards exactly match the boundary map (rightminServerper tier, right fallback, highest-tier-first order). If Step 1's two halves drifted, this fails with the specific mismatch. This is the check that replaces the old hardware unit test. - Dispatch wiring —
npm run docs:api-manifestre-extracts the manifest from the AST; the>= Nbranches from Step 2 + thesdkV<N>.bsfilename now show the new tier range on the affected endpoints. A missing branch shows up as drift in the committed manifest (CI-gated). - Boundary validity —
npm run test:scriptsrunsversion-boundaries.test.js(loads + validates the real committed map; a malformed Step 1 edit fails here),apiversion-consistency.test.js, and the manifest drift gate.npm run lint:docschecks the doc references. - Optional, not required — extend
tests/source/unit/utils/resolveApiVersion.spec.bswith a>= X→Ncase if you want belt-and-suspenders runtime coverage, but the static gate above already proves the twin is correct offline. Don't block the tier on a device being reachable.
Step 6b — Docs + fingerprint
- Prose twins — update
docs/dev/jellyfin-server-versioning.md(the tier tables + "Version Detection" steps) andsource/api/CLAUDE.md's "10.7–10.8 (V1) / 10.9+ (V2)" line to include the new tier. Alsodocs/user/jellyfin-server-feature-matrix.mdif user-facing support changed. - Fingerprint — no new fingerprint for the RC/master that prompted this; the next acknowledged stable release commits its fingerprint via the normal
/server-upgradeflow. The floor (10.7.0) never moves, so the backward/symmetry checks need no change.
Step 7 — Capture the decision + close out
Standing up a tier is decision-shaped (it closes off cramming the new shape into the existing tier). Offer to invoke /log decision with a slug like apiversion-v<N>-split capturing why X warranted a new tier. Summarize the surfaces touched and flag any TODO(V<N>) stubs left for when the spec finalizes. If this came from a /server-upgrade <rc> triage, remind the user to re-run that against the FINAL stable when it ships (the RC can still change).
When NOT to use
- A routine new endpoint or additive field on a current-tier server → use the existing
if m.getApiVersion() >= 2shape directly; no new tier. - A registry-schema change for a user setting → that's
/new-migration. - You just want to know what an RC/master build changes →
/server-upgrade <rc-or-unstable>(this skill is the follow-up when that triage says a new tier is warranted).
Sub-agent invocation
To invoke from a parent sub-agent: parent passes Read .claude/skills/new-api-version/SKILL.md and follow Steps 0-7 to stand up apiVersion tier N for server version X=<version>, wrapping the jellyfin-server-versioning.md recipe; keep jellyfin-version-boundaries.yml, resolveApiVersion(), and the sdkVN.bs/manifest-clamp convention in lockstep; scaffold the shim + dispatch branches and stop at each validator in the Task prompt.
Signals
- GitHub stars
- 41
- Forks
- 2
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
new-api-version- Source
- github.com/jellyrock/jellyrock