For bridge authors

Custom catalog API

Host a JSON endpoint, add the URL under Sources → JSON, and your library shows up in JAVP as posters, shelves, series and episodes.

JAVP scrapes nothing. Your bridge maps your own library - Plex, Jellyfin, a folder of files, magnets you have the rights to - into the schema below.

Modes

ModeWhen to useHow JAVP uses it
v1 - Bulk dumpSmall/medium libraries (rough guide: under ~5–10k titles)One GET → import all items into on-device catalog
v2 - Query APILarge librariesSearch / browse / page remotely; only cache what the user opens

v1 and v2 are both implemented. Prefer v2 when the full dump would be huge.

App-side search (Home / Search) filters locally synced items and, for v2 sources, also calls /search.

Optional access token

Premium / private catalogs may require auth. In Sources → JSON, open the Access token expandable and paste a token. JAVP sends it on every catalog HTTP request:

Authorization: Bearer <token>

If the pasted value already starts with Bearer , it is sent unchanged. 401 / 403 surface as an auth error (edit the source and update the token). Do not put tokens in javp://add / App Link URLs - add the catalog URL first, then paste the token in the app.

Item-level httpHeaders remain for playback only; they are separate from catalog auth. Catalog-root playHeaders / userAgent are also playback-only (inherited by items that omit their own).

Conventions

TopicRule
JSON typesObjects and arrays unless a field table says a string is also accepted
Language codesPrefer ISO 639-1 (ja, en, fr). jpn / eng / fra are accepted. Arrays or CSV: ["ja","en"] or "ja,en"
AliasesListed in each field table. First name is canonical
Times*Ms is milliseconds. Segment start / end (no Ms) are seconds
Empty vs omitOmit unused fields. "" / [] / null are treated as missing except vastUrl: false / "" (disable ads)
IDsStable strings. Re-sync updates the same id instead of duplicating

v1 - Bulk catalog

Request

GET {catalogUrl}
Accept: application/json

Response - object form (preferred)

{
  "name": "My Home Library",
  "version": 1,
  "vastUrl": "https://ads.example.com/vast.xml",
  "min_version": "0.4.3",
  "userAgent": "MyBridge/1.0",
  "playHeaders": {
    "Referer": "https://cdn.example.com/"
  },
  "items": [
    {
      "id": "movie-42",
      "title": "Big Buck Bunny",
      "playUrl": "https://cdn.example.com/bbb.mp4",
      "kind": "vod",
      "thumbnailUrl": "https://cdn.example.com/bbb.jpg",
      "group": "Open Movies",
      "subtitle": "2008 · Blender Foundation",
      "durationMs": 596000,
      "audioLanguages": ["en"],
      "subtitleLanguages": ["en", "fr"],
      "subtitles": [
        {
          "url": "https://cdn.example.com/bbb.en.vtt",
          "language": "en",
          "label": "English",
          "default": true
        }
      ],
      "segments": [
        { "type": "intro", "startMs": 0, "endMs": 90000 }
      ]
    }
  ]
}

Response - bare array (also accepted)

[
  {
    "title": "Live News",
    "url": "https://cdn.example.com/news.m3u8",
    "kind": "live"
  }
]

Aliases for the items list: items, entries, or media.

Catalog root

Fields on the top-level object (v1 dump or v2 descriptor). A bare array of items is also accepted (no root object).

FieldRequiredTypeNotes
namenostringDisplay name in Sources
versionnonumber1 = bulk dump (default). 2 = query API. A non-empty capabilities array also selects v2
itemsv1 yesarrayTitle rows. Aliases: entries, media. v2 may omit this (descriptor only) or include a warm cache
capabilitiesv2string[]Advertised endpoints. Tokens: search, browse, groups. search enables remote Search
itemCountnonumberApproximate library size (v2 UI)
userAgentnostringDefault playback User-Agent for every item / episode / variant that omits its own. Aliases: user-agent, ua
playHeadersnoobjectDefault playback HTTP headers (Referer, Origin, …). Aliases: httpHeaders, headers, playHttpHeaders. Not catalog-fetch auth
vastUrlnostring | falseCatalog-wide VAST/VMAP tag. Aliases: vast, prerollUrl, ads.vastUrl
min_versionnostringMinimum JAVP version. Alias: minVersion
platformsnostring | string[]Client allow-list. Alias: platform
requiresnostring | string[]Required client features. Aliases: require, needs
sourcesnoobject[]Named backends (see below). Alias: catalogSources

Optional client gating (min_version, platforms, requires)

Catalogs that rely on a newer JAVP, a specific device, or a client feature (torrents, …) may declare constraints. Omitted fields = any JAVP can use it.

{
  "name": "My Library",
  "version": 1,
  "min_version": "0.4.3",
  "platforms": ["android", "windows", "linux", "macos"],
  "requires": ["torrents"],
  "items": [ ]
}
FieldRequiredNotes
min_versionnoAlias: minVersion. Pubspec-style string (0.4.3, 0.4.3+57). Compared to the running JAVP version (major.minor.patch). A 0.4.3-dev install satisfies 0.4.3.
platformsnoAlias: platform (string or list). Allow-list of OS / form-factor tokens. Empty / omitted = all. See tokens below.
requiresnoAliases: require, needs. Client features that must be present. Unknown tokens are ignored.

Root-level mismatch (this whole catalog is not for this install): sync fails with a message. Existing cached titles are left as-is.

Item / variant / episode mismatch: that row is skipped; the rest of the catalog still syncs.

Platform tokens (any match allows):

TokenMatches
android, windows, linux, macos, ios, tizen, webos, webThat OS / host
tvAndroid TV, Tizen, webOS
desktopWindows / Linux / macOS pointer UI
mobilePhone / tablet (not TV)
android_tvAndroid + TV only

Aliases: winwindows, macmacos, samsungtizen, lgwebos, androidtv / firetvandroid_tv.

requires tokens: torrents (aliases torrent, p2p, magnets), downloads.

Magnet / .torrent playUrls are also skipped automatically when the client has no torrent engine (Smart TV ports), even if requires is omitted.

Named sources[] (optional)

A catalog may advertise several backends and gate them independently. Items and playVariants point at a source with source (alias catalogSource).

{
  "name": "Mixed Library",
  "version": 1,
  "sources": [
    { "id": "http", "name": "Direct streams" },
    { "id": "p2p", "name": "Magnets", "requires": ["torrents"] }
  ],
  "items": [
    { "id": "bunny-http", "title": "Bunny", "playUrl": "https://cdn.example.com/bbb.mp4", "source": "http" },
    {
      "id": "bunny",
      "title": "Bunny",
      "playVariants": [
        { "id": "http", "label": "HTTP", "playUrl": "https://cdn.example.com/bbb.mp4", "source": "http" },
        { "id": "p2p", "label": "Torrent", "playUrl": "magnet:?xt=urn:btih:…", "source": "p2p" }
      ]
    }
  ]
}
FieldRequiredTypeNotes
idyesstringToken items/variants use in source. Aliases: key, name (if id omitted)
namenostringLabel for this backend
min_versionnostringSame as catalog root
platformsnostring | string[]Same as catalog root
requiresnostring | string[]Same as catalog root

Unsupported named sources are omitted (not a catalog-wide error). The same min_version / platforms / requires fields work on items and variants.

JAVP also sends client identity on every catalog HTTP request so v2 bridges can filter server-side:

X-JAVP-Version: 0.4.3+57
X-JAVP-Platform: android
X-JAVP-Device: tv
X-JAVP-Capabilities: torrents,downloads

Query aliases (prefixed so they do not collide with bridge version): javp_version, javp_platform, javp_device, javp_capabilities (comma-separated, same tokens as the header), plus the existing locale.

Desktop (Windows / Linux / macOS) advertises torrents when the engine is built in. Gate magnets with requires: ["torrents"] - do not also set platforms: ["android"] or desktop clients will look like they cannot play magnets (javp_platform is windows / linux / macos).

Item fields

FieldRequiredNotes
titleyesDisplay name
playUrlyes*Stream URL, local path, or magnet:?…. *Not required for kind: "series" shells or rows that only have playVariants
url / streamUrl*Aliases for playUrl
idnoStable id (recommended). Auto-generated if omitted - breaks clean re-sync
kindnovod (default), live, series, network, local, catchup
thumbnailUrlnoPoster / logo. Aliases: poster, logo, still, stillUrl, image, imageUrl
groupnoCategory / shelf. Alias: category
subtitlenoSecondary display line (year, genre…) - not a caption track
durationMsnoDuration in milliseconds
channelId / streamId / epgChannelIdnoIPTV-style ids
catchupDaysnoArchive window for live/catchup
tmdbIdnoTMDB movie/TV id (number or numeric string). Put this on /search, /browse, and /items/{id} shells - JAVP uses it for episode stills, skip-intro, and enrichment without guessing the title. Catalogs that only have anilistId or mal: tags should set tmdbId themselves. Aliases: tmdb_id, tmdb. Optional tags: ["tmdb:304820"]
anilistIdnoAnime-list media id (int). Also accepted on search/browse, not only /items/{id}. Catalog ids anilist-123 are parsed. Aliases: anilist_id, anilist. Not used to look up TMDB
imdbIdnoIMDb id (tt…) for IntroDB / TheIntroDB
tvdbIdnoTheTVDB id
torrentFile / fileHintnoPreferred file name (or substring) inside a multi-file magnet
poster / posterUrlnoPortrait poster (preferred for VOD shelves)
backdrop / backdropUrlnoBackdrop / hero art
plot / descriptionnoSynopsis for the detail screen
genresnoArray of strings, or comma-separated string
ratingnoNumeric quality rating (e.g. 7.8). Not used as a popularity score
popularitynoCatalog-local heat, higher = hotter. Any non-negative scale (seeders, 0–100, play counts, …). JAVP percentile-normalizes per catalog so sources do not fight. Aliases: popular, pop, heat. Optional popularityRank / popularity_rank / popularRank (1 = hottest) if you only have a rank - inverted to a heat. Do not send a generic rank. If both heat and rank are set, heat wins
yearnoRelease year
releaseDatenoISO date string when known
trailerUrlnoDirect trailer URL (or YouTube watch URL)
trailerKey / youtubeTrailernoYouTube video id
castno["Name"] or [{ "name", "character", "profileUrl", "order" }]
season / seasonNumbernoEpisode season
episode / episodeNumbernoEpisode number
seriesId / parentIdnoLink episode rows to a series shell id
seasonsnoNested season/episode tree on a series shell (see below)
audioLanguagesno["ja","en"] or "ja,en". Aliases: audio, audioLangs
subtitleLanguagesnoKnown subtitle langs. Aliases: subLanguages, subtitleLangs, subs
subtitlesnoExternal subtitle files. Alias: externalSubtitles
audioTracksnoExternal audio files. Aliases: externalAudio, audioFiles
httpHeaders / headersnoMap of headers used when opening the stream. Aliases: playHeaders, playHttpHeaders
userAgent / user-agent / uanoPlayback User-Agent for playUrl (overrides the default JAVP and any User-Agent in httpHeaders)
drm / drmScheme / licenseUrlnoMarks the row as DRM-protected (Widevine / similar). JAVP cannot play these yet - it shows a clear error and offers an external player. Alias: nested { "scheme", "licenseUrl" }
segmentsnoSkip windows (intro / credits / …)
playVariants / variantsnoDistinct streams for this title (see Play variants)
source / catalogSourcenoNamed catalog sources[] id - gated with that source
min_version / platforms / requiresnoSame meaning as catalog root; unmatched rows are skipped
contentRating / certificationnoe.g. PG-13, TV-MA
adult / isAdult / is_adultnoWhen true/1, parental lock hides this row. Missing ⇒ not adult. Prefer camelCase adult
studio / networknoStudio or network label
originalTitlenoOriginal-language title
tagsnoFreeform tags (array or CSV)
resolutionnoe.g. 1080p, 4K. On a single HLS/DASH playUrl this is optional metadata - renditions stay in the player
videoCodec / audioCodecnoe.g. hevc, aac
hdrnoe.g. HDR10, DV
updatedAtnoISO timestamp for the row
vastUrlnoPer-title VAST/VMAP tag (overrides catalog vastUrl). Empty / false disables ads for this row

Rows missing title, or missing a playable URL when not a series shell / variant parent, are skipped.

Popularity

rating is quality. For Catalog Popular sort, send a catalog-local heat on each row:

{ "id": "movie-42", "title": "Big Buck Bunny", "playUrl": "…", "popularity": 1840 }

Any non-negative number works - torrent seeders, a 0–100 score, watch counts. JAVP does not treat this as a global 0–100. Each catalog is percentile-normalized on device so a seeder column cannot swamp another source’s 0–100. TMDB trending/popular still ranks first when a tmdbId matches; catalog heat is the fallback before rating / year.

If you only have a 1-based chart position, send popularityRank (1 = hottest) instead. Generic rank is ignored.

JAVP currently sorts Popular on device after fetch. A future v2 /browse?sort=popular is optional; including popularity on /browse and v1 dump items is enough.

When a parental PIN is set and the session is locked, JAVP hides rows with adult: true (same path as Xtream is_adult). Unlocking with the PIN shows them again. Manual hidden Live groups remain separate.

Series

The series episode picker reads only:

  1. Nested seasons[].episodes[] on the series shell (preferred), or
  2. Flat catalog rows with seriesId (+ seasonNumber / episodeNumber)
RuleDetail
Shell playUrlOptional. Not required. Never treated as “the only playable thing.”
Empty seasonsEmpty episode UI (even if the shell has a magnet / playVariants)
Episode playUrlOptional on stubs; fill later via /items/{episodeId} or include on /items/{id}
Episode artOptional thumbnailUrl (aliases: poster, still, stillUrl, image, imageUrl, logo). Use a per-episode still, or omit it. Do not copy the series poster onto every episode (different size variants of the same cover file still count as the same image). Reused series art is treated as missing. With a BYO TMDB key and a catalog tmdbId, JAVP fills missing/generic thumbs and “Episode N” titles from TMDB
Shell playVariantsOptional show-level editions only - not the episode list

Option A - nested seasons on a shell (preferred for BYO / anime bridges):

{
  "id": "show-42",
  "title": "Example Show",
  "kind": "series",
  "anilistId": 1001,
  "tmdbId": 1001,
  "posterUrl": "https://cdn.example.com/show.jpg",
  "plot": "…",
  "seasons": [
    {
      "seasonNumber": 1,
      "name": "Season 1",
      "episodes": [
        {
          "id": "show-42-s1e1",
          "episodeNumber": 1,
          "title": "Episode 1",
          "thumbnailUrl": "https://cdn.example.com/s1e1-still.jpg",
          "playUrl": "magnet:?xt=urn:btih:…"
        }
      ]
    }
  ]
}

Option B - flat episode rows linked with seriesId:

[
  { "id": "show-1", "title": "Example Show", "kind": "series", "posterUrl": "…" },
  {
    "id": "show-1-s1e1",
    "title": "Pilot",
    "playUrl": "https://cdn.example.com/s1e1.mp4",
    "seriesId": "show-1",
    "seasonNumber": 1,
    "episodeNumber": 1
  }
]

Batch / multi-file magnets

It is valid to set the same batch magnet as playUrl on every episode (for example a season pack covering 01–28). JAVP’s torrent engine selects a file by:

  1. Optional torrentFile / fileHint on the episode, else
  2. Matching episodeNumber (and seasonNumber when present) against file names (S01E02, - 02 (, E02, …), else
  3. Largest streamable file (legacy fallback)

Seasons and episodes

Season object (seasons[])

FieldRequiredTypeWhat it does
seasonNumbernonumberSeason index. Alias: season. Default 0 if omitted
namenostringDisplay name. Default Season {n}
posterUrlnostringSeason poster. Alias: poster
episodesnoobject[]Episode list. Alias: items

Episode object (episodes[] or flat catalog row)

Nested episodes and /items/{id}/episodes rows. Flat v1 rows also use item fields (seriesId, kind, …).

FieldRequiredTypeWhat it does
idnostringStable episode id. Default {seriesId}-s{season}e{episode}
episodeNumbernonumberEpisode index. Alias: episode
seasonNumbernonumberOverrides the parent season. Alias: season
titlenostringEpisode title. Default Episode {n}
plotnostringSynopsis. Alias: description
thumbnailUrlnostringPer-episode still. Aliases: poster, posterUrl, still, stillUrl, image, imageUrl, logo. Do not copy the series poster
durationMsnonumberDuration in milliseconds
playUrlnostringStream / magnet. Alias: url. Omit on stubs (v2 progressive resolve)
playVariantsnoobject[] | string[]Distinct encodes for this episode (see Play variants). Alias: variants
torrentFilenostringFile inside a batch magnet. Alias: fileHint
resolutionnostringMetadata when there is a single playUrl
httpHeadersnoobjectPlayback headers (overlay catalog / series defaults)
userAgentnostringPlayback User-Agent for this episode
tmdbIdnonumberEpisode TMDB id when known
sourcenostringNamed sources[] id
min_version / platforms / requiresnosame as rootUnmatched episodes are skipped

Play variants

playVariants (alias variants) lists distinct streams for one title or episode: separate files, language-specific URLs, different DRM, or different files inside a magnet. Each unique playUrl (+ torrentFile when set) becomes a sibling encode (player Version).

Do not list HLS / DASH renditions as variants. One master playlist is enough - the player reads the ladder and offers Auto / 1080p / 4K there:

"playUrl": "https://cdn.example.com/movie.m3u8"

Tagging resolution on that single URL is optional metadata, not a Versions chip. The same playUrl listed twice (once as 1080p and once as 4K) is collapsed to one encode.

Each entry may be a URL string ("https://…/a.mp4") or an object:

"playVariants": [
  {
    "id": "ja-1080",
    "label": "Japanese 1080p",
    "playUrl": "https://cdn.example.com/ja-1080.mp4",
    "resolution": "1080p",
    "audioLanguages": ["ja"],
    "subtitleLanguages": ["en", "fr"],
    "httpHeaders": { "Referer": "https://cdn.example.com/" }
  },
  {
    "id": "fr-4k",
    "label": "French 4K",
    "playUrl": "https://cdn.example.com/fr-2160.mp4",
    "resolution": "4K",
    "hdr": "HDR10",
    "audioLanguages": ["fr"]
  }
]
FieldRequiredTypeWhat it does
playUrlyesstringStream URL, path, or magnet:?…. Aliases: url
idnostringStable id for this encode. Default {parentId}-v{index}
labelnostringVersion chip text. Aliases: title, name. Falls back to resolution, then "Version"
subtitlenostringExtra display line (not a caption track). Shown next to label
resolutionnostringe.g. 1080p, 4K. Metadata for the Version row
videoCodecnostringe.g. hevc, av1
audioCodecnostringe.g. aac, eac3
hdrnostringe.g. HDR10, DV
torrentFilenostringFile name / substring inside a multi-file magnet. Alias: fileHint
audioLanguagesnostring[] | CSVSpoken languages in this stream. Aliases: audio, audioLangs. Shown on the title page and used to pick a default encode. Omit to inherit the parent item
subtitleLanguagesnostring[] | CSVCaption languages in this stream. Aliases: subLanguages, subtitleLangs, subs. Same inherit rule
subtitlesnoobject[]External caption files for this encode only. Alias: externalSubtitles. Inherit parent if omitted
audioTracksnoobject[]External audio files for this encode only. Aliases: externalAudio, audioFiles
httpHeadersnoobjectPlayback headers for this URL (overlay parent / catalog defaults). Aliases: headers, playHeaders, playHttpHeaders
userAgentnostringPlayback User-Agent for this URL. Aliases: user-agent, ua
sourcenostringNamed catalog sources[] id. Alias: catalogSource
drm / drmScheme / licenseUrlnostring | objectMarks this encode DRM-protected (unplayable in JAVP)
min_version / platforms / requiresnosame as rootUnmatched variants are skipped
WhereBehaviour
VOD / movie rowUnique playUrls expand to sibling catalog rows → title Versions. Same URL stays one encode (languages unioned)
Series shellIgnored for the episode picker
Nested episode / flat episode rowDistinct streams in the episode Version list; same URL collapsed

External subtitles

"subtitles": [
  {
    "url": "https://cdn.example.com/en.vtt",
    "language": "en",
    "label": "English",
    "default": true,
    "format": "vtt"
  },
  {
    "url": "https://cdn.example.com/en.forced.srt",
    "language": "en",
    "forced": true
  },
  {
    "url": "https://cdn.example.com/en.sdh.vtt",
    "language": "en",
    "sdh": true
  }
]
FieldRequiredTypeWhat it does
urlyesstringCaption file URL. Aliases: uri, src. A bare string in the array is treated as url
languagenostringISO language code (en, ja, fr). Alias: lang
labelnostringPlayer row text. Aliases: title, name
defaultnobooleanPrefer this track on play. Alias: isDefault
forcednobooleanForced / signs-only track. Alias: isForced
hearingImpairednobooleanSDH / CC. Aliases: sdh, cc
formatnostringHint: srt, vtt, ass. Alias: type

External audio

Sidecar audio (.mka, extra HLS audio, …) offered in the player audio-track menu. A bare URL string in the array is treated as { "url": "…" }.

"audioTracks": [
  { "url": "https://cdn.example.com/ja.mka", "language": "ja", "label": "Japanese", "default": true },
  { "url": "https://cdn.example.com/en.mka", "language": "en", "label": "English" }
]
FieldRequiredTypeWhat it does
urlyesstringAudio file URL. Aliases: uri, src
languagenostringISO language code. Alias: lang
labelnostringPlayer row text. Aliases: title, name
defaultnobooleanPrefer this track on play. Alias: isDefault

These are extra files attached to a stream. They are not a substitute for playVariants when each language is a different video URL - use variants + audioLanguages for that.

HTTP headers

Sent with the player (and downloads) when opening playUrl. Catalog-fetch auth (Authorization: Bearer from Sources → Access token) is separate and is not applied to streams.

Catalog root (optional defaults for every item / episode / variant):

{
  "name": "My Library",
  "userAgent": "MyBridge/1.0",
  "playHeaders": {
    "Referer": "https://cdn.example.com/",
    "Origin": "https://cdn.example.com"
  },
  "items": [ ]
}

Aliases at root: httpHeaders, headers, playHttpHeaders. userAgent aliases: user-agent, ua.

Item / variant / episode (overlay the catalog defaults; later wins):

"userAgent": "MyBridge/1.0",
"httpHeaders": {
  "Referer": "https://cdn.example.com/",
  "Authorization": "Bearer stream-token"
}

User-Agent may also be set inside httpHeaders. A dedicated userAgent field wins. Nested seasons[].episodes[] and playVariants[] accept the same fields.

Passed to the player when opening playUrl. On libmpv backends the user-agent property follows this value (instead of always sending JAVP).

Skip segments

Skip-intro / skip-credits windows for the player.

"segments": [
  { "type": "intro", "startMs": 90000, "endMs": 150000 },
  { "type": "credits", "startMs": 5400000 }
]
FieldRequiredTypeWhat it does
typeyesstringintro / opening, recap, credits / outro / endcredits, preview
startMsyes*numberStart in milliseconds. *start (seconds) is used when startMs is omitted
endMsnonumberEnd in milliseconds. Omit = from start to end of media (typical for credits). end (seconds) when endMs omitted
sourcenostringProvenance label (default catalog)
confidencenonumberOptional 0–1 hint

Cast

On a title (or v2 /items/{id}). Strings or objects:

"cast": [
  "Jane Doe",
  { "name": "John Smith", "character": "The Pilot", "profileUrl": "https://cdn.example.com/js.jpg", "order": 1 }
]
FieldRequiredTypeWhat it does
nameyesstringPerson name. A bare string in the array is this field
characternostringRole. Alias: role
profileUrlnostringHeadshot. Alias: image
ordernonumberSort index (default = array index)

Playback URLs

  • HTTP(S) progressive / HLS / DASH - played directly by media_kit
  • magnet:?… - resolved via BYO torrent engine to a localhost HTTP stream
  • File paths - treated like local media when applicable

v2 - Query API

Point Sources → JSON at the catalog root (descriptor). JAVP detects version: 2 or a non-empty capabilities array and does not expect a full item dump.

GET /catalog - describe the catalog

Your source URL may be this document, or any URL that returns:

{
  "name": "Huge Library",
  "version": 2,
  "min_version": "0.4.3",
  "platforms": ["android", "windows", "linux", "macos"],
  "capabilities": ["search", "browse", "groups"],
  "itemCount": 128400,
  "userAgent": "MyBridge/1.0",
  "playHeaders": { "Referer": "https://cdn.example.com/" }
}

Optional VAST 4.x tag (VAST 2/3/4.2 and VMAP):

{
  "name": "Huge Library",
  "version": 2,
  "vastUrl": "https://ads.example.com/vast.xml",
  "capabilities": ["search", "browse", "groups"]
}

Aliases: vast, prerollUrl, or ads.vastUrl. The player fetches the tag for VOD (not live / catch-up / offline). It plays linear prerolls, VMAP mid-rolls (timeOffset as a clock or percent), and post-rolls (timeOffset="end"). Wrappers are followed (max 5). Per-item vastUrl overrides the catalog tag; vastUrl: false or "" disables ads for that row.

The player is a linear VAST 4.2 client: progressive/HLS MediaFile, skipoffset, click-through, quartile + progress tracking, mute/pause/expand, AdChoices Icon overlays, image companions, closed captions, and MRC-style viewable impressions. It does not execute VPAID, SIMID, or OMID verification scripts (those creatives are skipped and verificationNotExecuted is pinged). Failed or empty tags fail open into the title.

If the same response also includes items, they are imported as a warm cache.

Root userAgent / playHeaders are remembered and inherited by later /search, /browse, /items/{id}, and episode responses unless those rows set their own.

Remote title search. Wired from in-app Search for v2 sources that list search in capabilities.

GET /search?q=bunny&page=1&limit=50&locale=fr
QueryRequiredTypeWhat it does
qyesstringSearch text
pagenonumber1-based page (default 1)
limitnonumberPage size (JAVP sends 50)
localenostringApp or device language (fr, en, ja) so the bridge can prefer matching audio / titles

JAVP also appends javp_version, javp_platform, javp_device when not already in the query.

{
  "query": "bunny",
  "page": 1,
  "limit": 50,
  "total": 3,
  "items": [ { "id": "…", "title": "…", "playUrl": "…", "kind": "vod" } ]
}
Response fieldTypeWhat it does
itemsobject[]Title rows (same schema as v1 items). Aliases: entries, media
querystringEcho of q (optional)
pagenumberEcho (default 1)
limitnumberEcho (default = items.length)
totalnumberTotal hits if known (default = items.length)
playHeaders / userAgentobject / stringOptional page-level playback defaults

HTTP 404 means search is unsupported; JAVP falls back to the on-device cache.

GET /browse

List by shelf/category without a text query. Include popularity (or popularityRank) on items so Catalog Popular can rank titles that are not on TMDB trending.

GET /browse?group=movies&page=1&limit=50&locale=fr
QueryRequiredTypeWhat it does
groupnostringShelf / category id from /groups (omit = mixed / home page)
pagenonumber1-based page (default 1)
limitnonumberPage size (JAVP sends 50)
localenostringSame as /search

Response shape is the same as /search (items, page, limit, total).

Optional query: locale

JAVP always sends the app language when the user picked one in Settings, otherwise the device language (BCP-47 language code, e.g. fr, en, ja) as locale on /search, /browse, /items/{id}, and /items/{id}/episodes.

Bridges may ignore it, or use it to prefer matching audio / release groups / localized titles. Especially useful on progressive episode resolve:

GET /items/{episodeId}?locale=fr

GET /items/{id}

Fetch one title (cast, trailer, nested seasons when available).

GET /items/{id}?locale=fr
QueryRequiredTypeWhat it does
localenostringPrefer matching audio / release group when filling playUrl

Response: a title object (same fields as a v1 item), or { "item": { … } }.

Series shells may return:

  • Full seasons[].episodes[] with playUrls, or
  • Season stubs / episode stubs without playUrl (progressive detail)

Optional: GET /items/{id}/episodes

Lazy episode list for a series (avoids resolving every magnet on first open).

GET /items/show-42/episodes?season=1&locale=fr
QueryRequiredTypeWhat it does
seasonnonumberSeason to list (default = first)
localenostringPrefer matching audio/subs
resolveno1 / true / yesFill playUrl / playVariants in this response (see bulk fill)
limitnonumberMax episodes when resolving (JAVP default 24, hard cap 24)
offsetnonumberSkip this many episodes before the fill window
{
  "season": 1,
  "episodes": [
    {
      "id": "show-42-s1e1",
      "episodeNumber": 1,
      "title": "Episode 1"
    }
  ]
}
Response fieldTypeWhat it does
season / seasonNumbernumberSeason these episodes belong to
episodesobject[]Episode objects (see Episode object). Aliases: items, entries
seasonsobject[]Alternate shape: full season tree instead of a flat episode list
resolvedbooleanOptional; true after a resolve=1 fill
(bare array)object[]Also accepted as the whole body

When episode stubs omit playUrl, JAVP resolves GET /items/{episodeId}?locale= with a cold-series gate (concurrency 1 until first success, then prefetch ≤ 2–3), and on Play / Versions if still cold. Prefer a short delay (~1s) after this response before background prefetch so bridges can warm search caches.

Bulk fill - resolve=1 (opt-in)

One search/index pass for the cour; response episodes include playUrl / playVariants when found. Use after stub paint (or instead of N parallel episode GETs) for short cours.

GET /items/show-42/episodes?season=1&locale=fr&resolve=1&limit=12
{
  "season": 1,
  "resolved": true,
  "episodes": [
    {
      "id": "show-42-s1e1",
      "episodeNumber": 1,
      "title": "Episode 1",
      "playUrl": "magnet:?xt=urn:btih:…",
      "playVariants": [
        {
          "id": "ja",
          "label": "Japanese",
          "playUrl": "magnet:?xt=urn:btih:…",
          "audioLanguages": ["ja"],
          "subtitleLanguages": ["en", "fr"]
        }
      ]
    }
  ]
}

Do not call resolve=1 for huge series without a tight limit/offset window. Empty playUrl on some rows is non-fatal - fall back to per-episode GET.

Optional: GET /groups

GET /groups
{
  "groups": [
    { "id": "movies", "name": "Movies", "count": 4200 },
    { "id": "kids", "name": "Kids", "count": 310 }
  ]
}

A bare array of group objects is also accepted.

FieldRequiredTypeWhat it does
idyes*stringValue sent as /browse?group=. Falls back to name
nameyes*stringShelf label. Falls back to id
countnonumberOptional title count for the UI

At least one of id / name must be non-empty.

Client behaviour

  1. Add source → detect version: 2 (or capabilities)
  2. Do not require a bulk items dump
  3. Search → call /search and cache hits locally
  4. Continue-watching / detail use cached rows + /items/{id} when needed
  5. Series progressive detail - first paint from /items/{seriesId} or /episodes (stubs OK). Then either:
    • Bulk (preferred for short cours): one GET /items/{id}/episodes?resolve=1&limit=≤24&locale= and merge playUrls into local cache, or
    • Per-episode: cold-series gate (concurrency 1 until first success; optional ~1s delay after stub /episodes); then prefetch visible rows at concurrency 2–3 max. Play / Versions still resolve on demand if cold.
  6. Simulcast / new episodes - shell episode lists are soft-revalidated on open after ~30 minutes (and on pull-to-refresh). New episode ids merge in; already resolved magnets are kept when the bridge still returns stubs for those ids

URL joining: if the source URL ends with /catalog or a .json file, query paths (/search, /browse, /groups, /items/{id}) are resolved from the parent directory - e.g. https://host/anime/cataloghttps://host/anime/search.

XML (optional later)

Same fields as JSON item objects. Not implemented yet - JSON is the supported format.

Tips for bridge authors

  1. Prefer stable ids so re-sync updates instead of duplicating.
  2. Keep v1 payloads under a few MB when possible; otherwise use v2.
  3. Use HTTPS. Cleartext HTTP works on Android for IPTV-style setups but is discouraged.
  4. For series, emit nested seasons (or flat seriesId rows). Shell magnets alone do not populate the episode UI.
  5. Magnets in playUrl are fine for legal BYO torrents only. Shared batch magnets across episodes are supported (file-by-episode selection).
  6. Put cast / trailer / seasons in the item (or v2 /items/{id}) so detail works without TMDB.
  7. Prefer anilistId and tmdbId on anime shells (search/browse/item). Map other anime ids to tmdbId in the catalog. tags: ["mal:…"] is optional and is not used by JAVP to look up TMDB.
  8. Do not set episode thumbnailUrl to the series cover. Leave it empty so TMDB stills can fill in, or ship real stills.
  9. Large series: prefer /items/{id}/episodes?season= or stub-then-fill episode ids.
  10. Set min_version / platforms / requires when the catalog (or an individual source / row) needs a newer JAVP, a specific device, or torrents (optional; unmatched catalogs refuse to sync, unmatched rows are skipped).

Websites can deep-link into JAVP so users add a catalog, M3U playlist, Xtream, or Stalker/Ministra login with one tap. The app always shows a confirm dialog before adding.

Prefer HTTPS App Links for public “Add to JAVP” buttons:

https://javp.app/add?type=custom&url=https%3A%2F%2Fexample.com%2Fcatalog.json&name=My%20Library
  • App installed (verified Android App Links): opens JAVP directly.
  • App missing: the browser loads /add - install from updater.javp.app, then open the same link again. There is no automatic add after install (Play Store listing URLs also cannot carry that without Install Referrer plumbing).

Keep javp://add?… for QR codes, TV pairing paste, and places that cannot use HTTPS.

To build either form without hand-encoding query strings, use the add link maker on the website (?type=custom, ?type=m3u, ?type=xtream, or ?type=stalker opens that mode directly).

HTML example:

<a href="https://javp.app/add?type=custom&url=https%3A%2F%2Fexample.com%2Fcatalog.json&name=My%20Library">
  Add to JAVP
</a>

Custom JSON catalog

https://javp.app/add?type=custom&url=https%3A%2F%2Fexample.com%2Fcatalog.json&name=My%20Library
javp://add?type=custom&url=https%3A%2F%2Fexample.com%2Fcatalog.json&name=My%20Library
QueryRequiredNotes
typeyescustom, or aliases json / catalog
urlyeshttp/https catalog URL (also catalog / playlist)
namenoDisplay name in Sources

M3U playlist

https://javp.app/add?type=m3u&url=https%3A%2F%2Fexample.com%2Flist.m3u&name=My%20IPTV&epg=https%3A%2F%2Fexample.com%2Fepg.xml
javp://add?type=m3u&url=https%3A%2F%2Fexample.com%2Flist.m3u&name=My%20IPTV&epg=https%3A%2F%2Fexample.com%2Fepg.xml
QueryRequiredNotes
typeyesm3u, or aliases m3u8 / playlist
urlyesPlaylist URL
namenoDisplay name
epgnoOptional EPG XML URL (epgUrl also accepted)

Xtream Codes

https://javp.app/add?type=xtream&url=http%3A%2F%2Fexample.com%3A8080&username=user&password=pass&name=My%20IPTV&alt=http%3A%2F%2Falt.example.com
javp://add?type=xtream&url=http%3A%2F%2Fexample.com%3A8080&username=user&password=pass&name=My%20IPTV&alt=http%3A%2F%2Falt.example.com
QueryRequiredNotes
typeyesxtream, or aliases xc / xtream-codes
urlyesServer DNS (server / host / dns also accepted)
usernameyesAlso user / login
passwordyesAlso pass / pwd
namenoDisplay name
altnoOptional Samsung/LG DNS (alternate / altDns / dns2)

The confirm dialog shows server and username only - the password is never displayed. Prefer HTTPS when the portal supports it; credentials in a URL can appear in browser history and share sheets.

Stalker / Ministra

https://javp.app/add?type=stalker&url=http%3A%2F%2Fportal.example.com&mac=00%3A1A%3A79%3A12%3A34%3A56&name=My%20Portal&serial=ABC123
javp://add?type=stalker&url=http%3A%2F%2Fportal.example.com&mac=00%3A1A%3A79%3A12%3A34%3A56&name=My%20Portal&serial=ABC123
QueryRequiredNotes
typeyesstalker, or aliases ministra / mag / portal
urlyesPortal URL (portal / server / serverUrl / host also accepted)
macyesDevice MAC (username / user also accepted)
serialnoOptional device serial (password / pass / sn also accepted)
namenoDisplay name

The confirm dialog shows portal and MAC. Prefer HTTPS when the portal supports it; MAC (and serial) in a URL can appear in browser history and share sheets.

If the same type + URL (or Xtream server + username, or Stalker portal + MAC) is already added, JAVP offers a re-sync instead of duplicating.

Jellyfin / Emby / Plex are not supported via deep link.

Phone deep link vs Android TV QR

SurfaceHow
Phone / tablet with JAVPTap https://javp.app/add?… (or javp://add?… / QR) → confirm dialog → source syncs on that device
Android TV / desktop hostPair device shows a https://javp.app/pair QR → phone with JAVP opens push/pull (LAN, token + PIN). Without the app, the landing page offers install or Continue in browser (LAN form for one javp://add / javp-sources.json)

Publishers can put an “Add to JAVP” HTTPS link on the website for phones. For TV setup: open Sources → Pair device (or Settings → Profiles), scan with a phone that has JAVP to push sources, or use the browser form URL shown under the QR.

Where it shows up in the app

UIBehaviour
Sources → JSONAdd catalog URL and sync (v1 dump or v2 descriptor). Optional Access token under an expandable for private catalogs
https://javp.app/add?… / javp://add?…Confirm → add custom / M3U / Xtream / Stalker source and sync
TV / desktop pairinghttps://javp.app/pair QR → app push/pull (or browser landing / LAN form)
Home / SearchLocal filter + v2 remote /search
Title detailCast, trailer, audio/subs, versions, tech tags
PlayerHeaders, external audio/subs, skip segments
Library → magnetSeparate BYO torrent entry (not a remote catalog)

Building a bridge and something here does not match what the app does? Email [email protected].