API Architecture Layering Guide
This document defines the standardized approach for making API calls in JellyRock, ensuring consistent patterns across the codebase.
Overview
Section titled “Overview”The API architecture follows a 3-layer abstraction model, where each layer builds upon the one below it. Developers should use the highest-level layer that meets their specific use case.
┌─────────────────────────────────────────────────────────────┐│ Layer 3: Domain Helpers ││ (source/api/imageHelpers.bs) ││ • Type-safe node wrappers ││ • JellyfinUser, JellyfinBaseItem specific functions │└─────────────────────────────────────────────────────────────┘ ↓┌─────────────────────────────────────────────────────────────┐│ Layer 2: Business Logic Utilities ││ (source/api/image.bs) ││ • Validation + defaults + error handling ││ • Prevents 404s, sets standard dimensions │└─────────────────────────────────────────────────────────────┘ ↓┌─────────────────────────────────────────────────────────────┐│ Layer 1: Smart API Client ││ (source/api/ApiClient.bs) ││ • Direct API endpoint calls ││ • V1/V2 server version dispatch ││ • Smart defaults: image params, fields auto-augmentation │└─────────────────────────────────────────────────────────────┘Layer 1: ApiClient (Foundation)
Section titled “Layer 1: ApiClient (Foundation)”File: source/api/ApiClient.bs
Access: GetApi().<method>()
The ApiClient provides direct access to Jellyfin API endpoints with automatic V1/V2 server version dispatch. It automatically injects image parameters and version-specific fields to ensure consistent, bulletproof item fetching across all Jellyfin server versions (10.7.0+).
Automatic Parameter Injection
Section titled “Automatic Parameter Injection”The following defaults are automatically applied to item fetching endpoints:
| Parameter | Default Value | Purpose |
|---|---|---|
EnableImageTypes | "Primary,Backdrop,Logo,Thumb" | Ensures images are requested |
ImageTypeLimit | 1 | Limits images per type |
fields | Auto-augmented* | Adds required fields (below) |
Auto-augmented fields appended to fields parameter:
PrimaryImageAspectRatio- Always added (causesImageTagsto be returned)Chapters- Always addedTrickplay- Added forV2+ servers (10.9+)
These injections ensure consistent item data across all supported server versions without manual parameter management.
When to Use Layer 1
Section titled “When to Use Layer 1”- You need control over parameters (defaults can be overridden by passing your own values)
- Building custom helper functions (Layers 2-3)
- Working with non-standard endpoints
- Need automatic image parameter injection for consistent item fetching
Image Methods
Section titled “Image Methods”| Method | Purpose | Example Endpoint |
|---|---|---|
GetImageURL(id, type, index, params) | Item images only | /items/{id}/images/{type}/{index} |
GetUserImageURL(id, type, index, params) | User avatars only | V1: /users/{id}/images/{type}/{index}V2: /UserImage?userId={id} |
⚠️ Critical: Always use the correct method for the resource type:
- Use
GetUserImageURL()for user avatars - Use
GetImageURL()for items (movies, episodes, etc.)
Layer 1 Example
Section titled “Layer 1 Example”' Direct API call with V1/V2 dispatch (image methods have no defaults)url = GetApi().GetUserImageURL(userId, "primary", 0, { maxHeight: 300, maxWidth: 300, quality: 90})' Returns: "http://server:8096/users/abc123/images/primary/0?maxHeight=300..." (V1)' Or: "http://server:8096/UserImage?userId=abc123&type=primary..." (V2)Layer 2: Business Logic (Validation & Defaults)
Section titled “Layer 2: Business Logic (Validation & Defaults)”File: source/api/image.bs
Import: import "pkg:/source/api/image.bs"
This layer adds:
- Tag validation: Prevents
404errors by checking if image tags exist/are valid - Sensible defaults: Standard dimensions and quality settings
- Error handling: Returns empty string instead of invalid
When to Use Layer 2
Section titled “When to Use Layer 2”- Loading images where you have raw IDs and tags
- Need standard dimensions without node objects
- Want 404 prevention without full helper wrapper
Layer 2 Functions
Section titled “Layer 2 Functions”| Function | Purpose | Defaults |
|---|---|---|
ImageURL(id, version, params) | Item images | maxHeight: 384, maxWidth: 196, quality: 90 |
UserImageURL(id, params) | User avatars | maxHeight: 300, maxWidth: 300, quality: 90 |
Key Feature: Tag Validation
Section titled “Key Feature: Tag Validation”Both functions validate the tag parameter:
- If
tagis provided but invalid/empty → returns""(prevents broken image) - If
tagis valid → proceeds with URL generation
Layer 2 Example
Section titled “Layer 2 Example”' With validation and defaultsurl = UserImageURL(userId, { tag: user.primaryImageTag, maxHeight: 36, maxWidth: 36})' Returns: "" if tag is invalid' Or: valid URL with defaults appliedLayer 3: Domain Helpers (Type-Safe Wrappers)
Section titled “Layer 3: Domain Helpers (Type-Safe Wrappers)”File: source/api/imageHelpers.bs
Import: import "pkg:/source/api/imageHelpers.bs"
The highest-level layer provides type-safe, node-specific functions that extract data from Jellyfin content nodes automatically.
When to Use Layer 3
Section titled “When to Use Layer 3”- Working with
JellyfinUserorJellyfinBaseItemnodes - Need fallbacks (try primary, then thumb, then parent, etc.)
- Want simplest possible API for common operations
Layer 3 Functions
Section titled “Layer 3 Functions”| Function | Input | Fallback Chain |
|---|---|---|
GetPosterURLFromItem(item, maxH, maxW) | JellyfinBaseItem | primary → thumb → parentPrimary → parentThumb → seriesPrimary → backdrop |
GetBackdropURLFromItem(item, maxH, maxW) | JellyfinBaseItem | backdrop → parentBackdrop |
GetLogoURLFromItem(item, maxH, maxW) | JellyfinBaseItem | logo only |
GetUserAvatarURL(user, maxH, maxW) | JellyfinUser | primary only (with validation) |
Layer 3 Example
Section titled “Layer 3 Example”' Simplest usage - handles everythinguserImage.uri = GetUserAvatarURL(m.global.user, 36, 36)' Returns: "" if no valid image' Or: valid URL with all validation and defaultsDecision Tree
Section titled “Decision Tree”Use this flowchart to determine which layer to use:
┌──────────────────────────────┐│ Do you have a JellyfinUser ││ or JellyfinBaseItem node? │└──────────────────────────────┘ │ ┌───────┴───────┐ ▼ ▼ Yes No │ │ ▼ ▼┌─────────────┐ ┌──────────────────────────┐│ Use Layer 3 │ │ Do you have an image tag ││ (Helpers) │ │ and want validation? │└─────────────┘ └──────────────────────────┘ │ ┌───────┴───────┐ ▼ ▼ Yes No │ │ ▼ ▼ ┌─────────────┐ ┌─────────────────────────┐ │ Use Layer 2 │ │ Do you need full param │ │ (Image.bs) │ │ control or custom logic?│ └─────────────┘ └─────────────────────────┘ │ ┌───────┴───────┐ ▼ ▼ Yes No │ │ ▼ ▼ ┌─────────────┐ ┌──────────────┐ │ Use Layer 1 │ │ Use Layer 2 │ │ (ApiClient) │ │ (Image.bs) │ └─────────────┘ └──────────────┘Common Patterns
Section titled “Common Patterns”Loading User Avatar in a Component
Section titled “Loading User Avatar in a Component”import "pkg:/source/api/imageHelpers.bs"
sub loadUserImage() ' Layer 3: Cleanest, handles validation userImage.uri = GetUserAvatarURL(m.global.user, 36, 36)
if userImage.uri = "" ' No valid image - use fallback userImage.uri = "pkg:/images/icons/person_36px.png" end ifend subLoading Item Poster with Fallbacks
Section titled “Loading Item Poster with Fallbacks”import "pkg:/source/api/imageHelpers.bs"
sub loadItemPoster(item as object) ' Layer 3: Tries multiple image types automatically poster.uri = GetPosterURLFromItem(item, 440, 295)end subCustom Image with Specific Requirements
Section titled “Custom Image with Specific Requirements”import "pkg:/source/api/image.bs"
sub loadCustomImage(itemId, imageTag) ' Layer 2: Custom size with validation url = ImageURL(itemId, "Primary", { tag: imageTag, maxHeight: 100, maxWidth: 100, quality: 85 })
if url <> "" poster.uri = url end ifend subDirect API Access (Rare)
Section titled “Direct API Access (Rare)”' Layer 1: Image methods pass through without defaultsurl = GetApi().GetUserImageURL(userId, "primary", 0, { maxHeight: 600, maxWidth: 600, quality: 95})Anti-Patterns to Avoid
Section titled “Anti-Patterns to Avoid”❌ Don’t use item endpoints for users:
' WRONG - uses item endpoint for user imageuserImage.uri = GetApi().GetImageURL(userId, "primary", 0, params)✅ Correct:
' CORRECT - uses user endpointuserImage.uri = GetApi().GetUserImageURL(userId, "primary", 0, params)' Or better: use helperuserImage.uri = GetUserAvatarURL(user, 36, 36)❌ Don’t skip validation:
' WRONG - will 404 if tag is invalidparams = { tag: possiblyInvalidTag }url = GetApi().GetImageURL(id, "primary", 0, params)✅ Correct:
' CORRECT - validates tag firsturl = ImageURL(id, "primary", { tag: possiblyInvalidTag })if url <> "" ' Safe to useend ifTesting
Section titled “Testing”When writing tests for image URL generation:
- Layer 1 tests: Verify correct endpoint paths for
V1/V2(seesdk.versioning.spec.bs) - Layer 2 tests: Verify tag validation and defaults (see
ImageURL.spec.bs) - Layer 3 tests: Verify node property extraction and fallback chains
Migration Guide
Section titled “Migration Guide”If you encounter code using the wrong endpoint:
- Identify the resource type (user vs item)
- Check for node availability (
JellyfinUser/JellyfinBaseItem) - Select appropriate layer using decision tree above
- Update imports if needed
- Test on both
V1andV2servers
References
Section titled “References”source/api/ApiClient.bs- Layer 1: Raw API clientsource/api/image.bs- Layer 2: Business logic utilitiessource/api/imageHelpers.bs- Layer 3: Domain helperstests/source/unit/api/sdk.versioning.spec.bs-V1/V2endpoint teststests/source/unit/api/ImageURL.spec.bs- Validation testsdocs/dev/sdk-api-versioning.md-V1vsV2API differences