Skip to content
Donate

API Architecture Layering Guide

This document defines the standardized approach for making API calls in JellyRock, ensuring consistent patterns across the codebase.

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 │
└─────────────────────────────────────────────────────────────┘

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+).

The following defaults are automatically applied to item fetching endpoints:

ParameterDefault ValuePurpose
EnableImageTypes"Primary,Backdrop,Logo,Thumb"Ensures images are requested
ImageTypeLimit1Limits images per type
fieldsAuto-augmented*Adds required fields (below)

Auto-augmented fields appended to fields parameter:

  • PrimaryImageAspectRatio - Always added (causes ImageTags to be returned)
  • Chapters - Always added
  • Trickplay - Added for V2+ servers (10.9+)

These injections ensure consistent item data across all supported server versions without manual parameter management.

  • 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
MethodPurposeExample Endpoint
GetImageURL(id, type, index, params)Item images only/items/{id}/images/{type}/{index}
GetUserImageURL(id, type, index, params)User avatars onlyV1: /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.)
' 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 404 errors by checking if image tags exist/are valid
  • Sensible defaults: Standard dimensions and quality settings
  • Error handling: Returns empty string instead of invalid
  • Loading images where you have raw IDs and tags
  • Need standard dimensions without node objects
  • Want 404 prevention without full helper wrapper
FunctionPurposeDefaults
ImageURL(id, version, params)Item imagesmaxHeight: 384, maxWidth: 196, quality: 90
UserImageURL(id, params)User avatarsmaxHeight: 300, maxWidth: 300, quality: 90

Both functions validate the tag parameter:

  • If tag is provided but invalid/empty → returns "" (prevents broken image)
  • If tag is valid → proceeds with URL generation
' With validation and defaults
url = UserImageURL(userId, {
tag: user.primaryImageTag,
maxHeight: 36,
maxWidth: 36
})
' Returns: "" if tag is invalid
' Or: valid URL with defaults applied

Layer 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.

  • Working with JellyfinUser or JellyfinBaseItem nodes
  • Need fallbacks (try primary, then thumb, then parent, etc.)
  • Want simplest possible API for common operations
FunctionInputFallback Chain
GetPosterURLFromItem(item, maxH, maxW)JellyfinBaseItemprimary → thumb → parentPrimaryparentThumbseriesPrimary → backdrop
GetBackdropURLFromItem(item, maxH, maxW)JellyfinBaseItembackdrop → parentBackdrop
GetLogoURLFromItem(item, maxH, maxW)JellyfinBaseItemlogo only
GetUserAvatarURL(user, maxH, maxW)JellyfinUserprimary only (with validation)
' Simplest usage - handles everything
userImage.uri = GetUserAvatarURL(m.global.user, 36, 36)
' Returns: "" if no valid image
' Or: valid URL with all validation and defaults

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) │
└─────────────┘ └──────────────┘
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 if
end sub
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 sub
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 if
end sub
' Layer 1: Image methods pass through without defaults
url = GetApi().GetUserImageURL(userId, "primary", 0, {
maxHeight: 600,
maxWidth: 600,
quality: 95
})

Don’t use item endpoints for users:

' WRONG - uses item endpoint for user image
userImage.uri = GetApi().GetImageURL(userId, "primary", 0, params)

Correct:

' CORRECT - uses user endpoint
userImage.uri = GetApi().GetUserImageURL(userId, "primary", 0, params)
' Or better: use helper
userImage.uri = GetUserAvatarURL(user, 36, 36)

Don’t skip validation:

' WRONG - will 404 if tag is invalid
params = { tag: possiblyInvalidTag }
url = GetApi().GetImageURL(id, "primary", 0, params)

Correct:

' CORRECT - validates tag first
url = ImageURL(id, "primary", { tag: possiblyInvalidTag })
if url <> ""
' Safe to use
end if

When writing tests for image URL generation:

  • Layer 1 tests: Verify correct endpoint paths for V1/V2 (see sdk.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

If you encounter code using the wrong endpoint:

  1. Identify the resource type (user vs item)
  2. Check for node availability (JellyfinUser/JellyfinBaseItem)
  3. Select appropriate layer using decision tree above
  4. Update imports if needed
  5. Test on both V1 and V2 servers
  • source/api/ApiClient.bs - Layer 1: Raw API client
  • source/api/image.bs - Layer 2: Business logic utilities
  • source/api/imageHelpers.bs - Layer 3: Domain helpers
  • tests/source/unit/api/sdk.versioning.spec.bs - V1/V2 endpoint tests
  • tests/source/unit/api/ImageURL.spec.bs - Validation tests
  • docs/dev/sdk-api-versioning.md - V1 vs V2 API differences