Nyxovira Plugin API KapiTomo documentation

Create plugins for Nyxovira

A plugin connects Nyxovira to a reading site, shows the chapter list quickly, and prepares only the chapters selected by the user.

AI prompt · copy in one click

Generate an AI prompt to create your plugin

Enter a real work URL from the source and copy the complete request into the conversation with the AI that will create and validate the plugin.

Prompt ready to send to an AI assistant

Change the URL; the prompt updates automatically.

Official SDK resources

Open each published resource directly from this documentation.

Developer path

3. Validate the complete result

A plugin is ready only after a real chapter is downloaded, saved, and reopened successfully.

How a plugin works

The creator starts with plugin.json and browser/download_target.js. The script performs source-specific discovery and builds the chapter plan.

During development, the creator imports the local plugin folder into Nyxovira and tests it on the supported site.

The plugin may remain private. Publication is optional and happens only after testing.

Legacy manifests may omit the script and use the generic page detector, but new plugins need the script to pass the documented conformance workflow.

Choose the implementation level

1. Simple HTML

Use browser/download_target.js to read rendered HTML and produce the complete chapter plan.

2. Simple JSON API

Use the browser script for discovery and declare parser when the native downloader must repeat structured API requests outside the WebView.

3. Dynamic, protected, or encrypted API

parser is mandatory for endpoints, special headers, and decoding; the browser script still handles discovery and the browser session.

Parser optional only means that a manifest can be imported without one. It should be declared for a structured API, special headers, encryption, or native downloading outside the WebView.

Plugin files

Start with the manifest in a non-empty plugin folder. Add the browser script when site-specific recognition is needed.

my-plugin/
|-- plugin.json
`-- browser/
    `-- download_target.js
FilePurpose
plugin.jsonIdentifies the supported site. Start with the minimum fields required by Nyxovira.
browser/download_target.jsRequired for the new conformance workflow. A legacy manifest may import without it and fall back to generic detection.

plugin.json

Minimum required by Nyxovira

This is the smallest practical manifest for importing and testing a plugin:

{
  "id": "my-plugin",
  "match": { "hosts": ["source.invalid"] },
  "browser": {
    "home_url": "https://source.invalid/",
    "download_target_script_file": "browser/download_target.js"
  }
}

To load the source, Nyxovira only needs valid JSON, a non-empty folder name or id, at least one match.hosts entry, and browser.home_url. The example also names the browser script because it is the reliable way to recognize works and chapters. Without it, Nyxovira tries generic page detection, which may not understand every site.

Not required to import the plugin: schema_version, name, version, tags, browser.icon_mode, browser.short_label, GitHub, a catalog, or a plugin website. Declare parser whenever the source needs a structured API, special headers, encryption, or native downloading. If the site provides a stable public favicon or logo, use its URL in browser.icon_url; otherwise the icon may be omitted.

FieldWhen it is needed
idStrongly recommended; otherwise Nyxovira uses the folder name.
match.hostsRequired. Domains recognized by the plugin.
browser.home_urlRequired. Page opened by the app browser.
browser.download_target_script_fileRequired for a newly developed plugin; points to the discovery and chapter-plan script.
name, versionOptional for importing; useful for identification, sharing, and updates.
tagsNot needed by the plugin runtime. Add them only if an external catalog uses tags for organization or filtering.
browser.icon_urlOptional. Prefer the site's public favicon or logo when one is available.
parserRequired for structured APIs, special headers, encryption, or native downloading outside the WebView. It may be omitted only when the browser plan already contains final content.

Site mapping

Map the site's names to the fields Nyxovira expects: title, cover, chapters, text, and page images.

Discover the data source first

  • Look for official developer documentation, a public API, OpenAPI or Swagger files, GraphQL, JSON feeds, and documented endpoints.
  • If the API is not linked or documented, inspect the page source, loaded scripts, and the browser's network requests to identify the endpoints used by the site's own interface.
  • Compare the API data with the rendered HTML and use the most complete and stable source for works, chapters, text, and images.

Mandatory pre-ZIP map

  • Record a real work URL, reader URL, and the work, chapter, and page endpoints.
  • Record selectors, load completion, required headers, referer, cookies, login/tokens, and encryption.
  • Record paid or restricted chapters, source/display order, relative URL bases, and real ID formats.
  • Resolve at least one actual chapter into nonempty pages or paragraphs. Never invent endpoints, selectors, headers, keys, or results.
var plugin = {
  siteBaseUrl: "https://source.invalid",
  siteVariable: "SANITIZED_WORK_INDEX",
  siteRoutes: {
    detailsHash: "series",
    readerHash: "reader"
  },
  appRoutes: {
    publicSeriesPath: "manga",
    publicChapterPath: "chapter"
  },
  fields: {
    workId: "slug",
    workTitle: "name",
    workSummary: "synopsis",
    workCover: "cover_url",
    workChapters: "episodes",
    chapterParagraphs: "text_blocks",
    chapterPages: "page_urls",
    imageSource: "url"
  }
};

Instant chapter list

When the user taps download, set window.__nyxoviraChapterPlan immediately and return the canonical work URL.

window.__nyxoviraChapterPlan = JSON.stringify({
  title: "Work title",
  summary: "Short summary",
  canonicalUrl: "https://source.invalid/manga/work/",
  coverUrl: "https://source.invalid/cover.png",
  chapters: [
    {
      id: "chapter-forest-hunt",
      number: "1",
      title: "The Forest Hunt",
      contentType: "novel",
      paragraphs: ["First paragraph.", "Second paragraph."]
    }
  ]
});

return "https://source.invalid/manga/work/";

Prepare after selection

For large comics or APIs, keep the first plan lightweight. Load page URLs only after the user confirms selected chapters.

window.__nyxoviraPrepareDownloadPlan = function (context) {
  var selectedIds = Array.isArray(context.selectedChapterIds)
    ? context.selectedChapterIds
    : [];
  var plan = context.chapterPlan;

  plan.chapters.forEach(function (chapter) {
    if (selectedIds.length > 0 && selectedIds.indexOf(chapter.id) < 0) {
      return;
    }

    var payload = getJson(chapter.chapterDataPath);
    chapter.pages = payload.pages.map(function (page) {
      return page.imageUrl;
    });
    delete chapter.chapterDataPath;
  });

  window.__nyxoviraChapterPlan = JSON.stringify(plan);
  return plan;
};

Nyxovira passes { selectedChapterIds, chapterPlan }. The function may return the final plan object or a JSON string. If it returns nothing, the app keeps the original plan.

ID invariant: every chapterPlan.chapters[].id must exactly equal the corresponding value received in selectedChapterIds. Never add or remove an id: prefix, convert it to a slug, or replace it with the visible index.

Native parser contract

Supported adapters are next_payload, html_series, and aes_json_api. Route fields build work, chapter, reader, bootstrap, page, and CDN requests; request_headers supplies static API headers; map and *_keys map actual JSON paths; route prefixes and image hints control recognition; and encryption fields activate decoding with mapped key material. See the Markdown reference for the operational meaning of every field.

Open the complete parser field reference · Open plugin.schema.json

Headers, dynamic APIs, and encrypted APIs

Authentication / headers

Map referer, cookies, login, static headers, and per-page tokens separately. Authentication does not make the plugin public-only: it may use an authorized session already opened by the user and only the access granted to that session. A browser-plan page may use { url, headers }; Nyxovira carries those headers into the native image request. Never assume WebView cookies reach native requests unless the required value is explicitly included, and never request, store, export, or embed personal credentials, cookies, or tokens in plugin files or reports. Determine availability from the real authorized session, not from promotional copy or a generic notice.

Dynamic APIs

Keep discovery light, resolve only selected chapters, and exercise pagination or rotating tokens through the last page. The current in-app prepare hook must finish synchronously.

Encrypted APIs

Declare only legitimately observed formats and client-visible key material. Document the envelope fields, key/version selection, encoding, API origin and CDN origin separately. A declared format is not proof; content must decode and the complete chapter must be saved. See the complete synthetic encrypted API flow, including rotating S-box and AES/CBC variants.

Troubleshooting

For 401/403 check transport requirements; if 403 occurs before the plugin runs, report BLOCKED instead of calling the plugin invalid or bypassing access control. For missing chapters check selectors, pagination and mapping; for empty pages check URL bases, CDN and decoding. If page one works but a later page fails, the plugin is not valid.

Chapter formats

Use paragraphs for novels and pages for image chapters. The app also reads images for compatibility.

{
  "id": "chapter-forest-hunt",
  "title": "The Forest Hunt",
  "contentType": "novel",
  "paragraphs": [
    "First paragraph.",
    "Second paragraph."
  ]
}
{
  "id": "chapter-arrival",
  "title": "Arrival at the Ruins",
  "contentType": "images",
  "pages": [
    "https://source.invalid/page-001.png",
    "https://source.invalid/page-002.png"
  ]
}

Test in the web laboratory

The limited laboratory is the normal plugin development loop. It does not include the library, settings, catalogs, favorites, or permanent downloads. Nyxovira does not require catalog tags or a separately hosted icon to test a plugin. When the site already provides a public favicon or logo, use it in browser.icon_url.

Keep plugin.json and the browser folder together, then create one ZIP.

Open the Plugin Laboratory, upload the ZIP, and enter the URL of a real work.

The hosted test validates the manifest, mapping, chapter IDs, and the complete content of the first and last chapters of the supplied work.

Copy the JSON report. The ZIP, navigation, and collected content are released before the response is returned.

Conformance gate

Open the Web Plugin Laboratory

Upload the ZIP and enter the exact work URL that needs testing. The hosted service chooses the first and last chapters automatically and returns PLUGIN_VALID_FOR_TESTED_WORK only after downloading every page in both chapters with the Nyxovira request profile. The report records the ZIP SHA-256 and never claims that another work, chapter, ZIP, or previous installation was validated.

The result must contain temporaryDataReleased: true and stored: false. Without real network validation, state: not validated against the real site.

Complete sanitized examples: simple HTML, JSON API, novel, manga, and encrypted API. All use the reserved source.invalid domain.

Plugin creation ends after complete validation. Tags, schema_version, GitHub, a public catalog, and a plugin website are distribution metadata, not requirements for creating or importing the plugin. Use the site's favicon or logo when available; you do not need to host a separate icon.

Optional distribution after validation

If you later choose to share the finished plugin, host your own independent external catalog. This is a separate process and does not create another type of plugin.

External plugin store

This optional step comes only after plugins are ready and tested. It explains how to maintain an independent distribution; it is not a second plugin-creation mode.

A minimal external store can use this structure:

plugin-store/
|-- index.html
|-- catalog.json
`-- plugins/
    `-- my-plugin/
        |-- plugin.json
        `-- browser/
            `-- download_target.js

Host the folder at a public HTTPS address. The appearance, search, and plugin cards belong to the store itself; Nyxovira only needs to discover the catalog and receive the installation request. Add one object to the plugins list for every distributed plugin and identify its author and source site correctly.

Catalog discovery

Declare the catalog in the store's main page. A meta tag with the same name and a content URL is also supported.

<link rel="nyxovira-plugin-catalog" href="catalog.json">

Without a declaration, Nyxovira looks for catalog.json, catalog-store.json, and plugins.json in the page's directory. Users may also connect the JSON URL directly.

External catalog

{
  "schema_version": 1,
  "name": "My plugin store",
  "hub_url": "https://plugins.source.invalid/",
  "plugins": [{
    "id": "my-plugin",
    "name": "My Plugin",
    "author": "Author",
    "version": "1.0.0",
    "manifest_url": "plugins/my-plugin/plugin.json",
    "icon_url": "https://source.invalid/icon.png",
      "site_url": "https://source.invalid/",
      "status": "active"
  }]
}

hub_url identifies the storefront when a JSON URL is connected directly. store_url and homepage are accepted aliases. Relative URLs are resolved against the catalog URL. Repository-based entries may use repository_url, repository_ref, and plugin_path.

Direct installation

This complete example creates the button, explains when the page is open outside the app, and displays the result returned by Nyxovira.

<button id="install-my-plugin" type="button">Install My Plugin</button>
<p id="install-status" aria-live="polite"></p>

<script>
  const catalogUrl = new URL("catalog.json", location.href).href;
  const status = document.querySelector("#install-status");

  document.querySelector("#install-my-plugin").addEventListener("click", () => {
    const bridge = globalThis.NyxoviraAndroidBridge
      || globalThis.ArchiveInkAndroidBridge;

    if (!bridge || typeof bridge.installCommunityPlugin !== "function") {
      status.textContent = "Open this site from Nyxovira to install.";
      return;
    }

    try {
      const result = JSON.parse(
        bridge.installCommunityPlugin(
          catalogUrl,
          JSON.stringify({ id: "my-plugin" })
        ) || "{}"
      );
      status.textContent = result.message
        || (result.success ? "Plugin installed." : "Could not install the plugin.");
    } catch (error) {
      status.textContent = "Could not complete the installation.";
    }
  });
</script>

When the store is open in a regular browser, the example tells the person to open it from Nyxovira. Inside the app, the button installs the matching plugin from the connected catalog.

Before launching your external catalog

Host the page, catalog, and plugin files at public HTTPS addresses.

For every plugin, accurately identify the author, source site, and manifest path.

Test the Install button by opening the store from its card under External sites in Nyxovira.

Test before sharing

Publish every file over HTTPS.

In Nyxovira, open Sites → External sites and connect the store page or directory.

Open the connected site from its card and tap Install.

Confirm the message returned by the installation.

Final checklist

Validated plugin

Valid JSON, a non-empty folder name (or id), at least one supported host, a valid home URL, and a browser script tested against real content; use paragraphs for novels or pages for comics. A real chapter must finish, be saved, and reopen successfully.

If you later distribute it, follow the separate external-catalog section after this checklist.