PUBLIC API

PVZ: Voyage · API Docs

Interfaces ready — build what you need.

These public interfaces are intended for launchers, bots, mirrors, data tools, and DIY level clients. Clients should send an identifying User-Agent, respect Cache-Control and CDN-Cache-Control, avoid high-frequency polling, and handle failures through HTTP status codes; requests that do not follow the calling conventions may receive 404 or 429.

Command-line, script, official game-client, and server-side clients should send an identifying User-Agent; default tool UAs can receive a plain 404. View header policy

Three-step quick start

Most clients can start from the API index, then follow latest release and download entry points.

  1. Step 1

    Discover interfaces

    Read the API index to confirm families, schemas, locales, and cache policies.

    /api/v1/index.json
  2. Step 2

    Read latest release

    Request release detail and choose the platform key that matches the device.

    /api/v1/releases/latest.json
  3. Step 3

    Download and verify

    Download from builds.*.url, then use builds.*.sha256 to confirm that the local file matches the published build.

    /download/0.17q/windows

Endpoint catalog

Filter by use case. Every card exposes a copyable concrete example URL.

Discovery

API discovery

Cheap entry point listing families, schemas, versions, locales, and documentation links.

Release

Release and updates

Current version, platform builds, checksums, and changelog data.

Content

Field Guide and world data

Encyclopedia entries, world regions, DidYouKnow trivia, and localized prose.

DIY

DIY levels

Public level library, detail JSON, GDF downloads, and submission entry point.

Search/news

Search and news

Stable search corpus, announcement feed, and bot-friendly updates.

Downloads

Download routes

Server-backed binary downloads with HEAD and Range support.

RSS

RSS feeds

RSS 2.0 release feeds for feed readers.

GET · HEAD · DIY

DIY runtime configuration

Returns upload configuration that depends on request origin. This response is never publicly cached.

/api/v1/diy/runtime-config.json

GET · Discovery

API index

Machine-readable discovery entry point for launchers, bots, mirrors, and proxies, avoiding page scraping and hardcoded paths.

/api/v1/index.json

GET · Release

Latest release detail

Recommended release detail for launchers, mirrors, and bots. Includes every platform build.

/api/v1/releases/latest.json

GET · Release

Auto-update manifest

Updater manifest with backward-compatible top-level Windows x64 fields and a full builds matrix.

/api/v1/updates/latest.json

GET · Release

Release index

Lists public releases with the current latest version and detail links.

/api/v1/releases.json

GET · Release

Structured changelog

Returns both localized changelogs in one payload for announcement bots and launcher news panes.

/api/v1/changelog/0.17q.json

GET · Content

Field Guide index

Localized Field Guide catalog with counts, entry links, portrait assets, and alternate locale links.

/api/v1/encyclopedia/en/index.json

GET · Content

World region index

Localized world codex. There is no separate region-detail JSON; full paragraphs are included in regions.

/api/v1/world/en/index.json

POST · DIY

Submit a DIY level

Submits a .gdf file with public metadata. A successful response means the file was received and includes an owner credential.

/api/v1/diy/levels

POST · DIY

DIY owner: status

Uses the owner secret from an upload response to inspect or manage the owner's submission; the website can use the same interface through an HttpOnly owner session.

/api/v1/diy/levels/sample-level-id/owner/status

POST · DIY

DIY owner: edit

Uses the owner secret from an upload response to inspect or manage the owner's submission; the website can use the same interface through an HttpOnly owner session.

/api/v1/diy/levels/sample-level-id/owner/edit

POST · DIY

DIY owner: remove

Uses the owner secret from an upload response to inspect or manage the owner's submission; the website can use the same interface through an HttpOnly owner session.

/api/v1/diy/levels/sample-level-id/owner/remove

POST · DIY

DIY owner: update

Uses the owner secret from an upload response to inspect or manage the owner's submission; the website can use the same interface through an HttpOnly owner session.

/api/v1/diy/levels/sample-level-id/owner/update

GET · Search/news

Stable search corpus

Public wrapper for site-search documents. Do not depend on the root search-index files.

/api/v1/search/en.json

GET · Search/news

News and announcements JSON

Localized news feed for launchers, bots, and aggregators.

/api/v1/news/en.json

GET · HEAD · Downloads

Binary downloads

Download Windows and Android builds. Supports HEAD and single byte ranges; filenames are returned through Content-Disposition.

/download/0.17q/windows

GET · RSS

RSS release feed

RSS 2.0 feed with one item per release, linking to the localized changelog.

/feed.xml

Endpoint details

Open a panel for field summaries, trimmed responses, and curl/server-side fetch examples with the standard User-Agent.

GET · HEAD

DIY runtime configuration

/api/v1/diy/runtime-config.json
Best for
Site upload widget and game clients
Cache
Cache-Control: private, no-store
Example path
/api/v1/diy/runtime-config.json

Field summary

Field summary
FieldTypeDescription
mode string disabled, local, or production.
turnstileSiteKey string | null Public Turnstile site key.
turnstileRequired boolean Whether this request must provide a Turnstile token.

Runtime configuration example

{
  "schemaVersion": 1,
  "kind": "diyRuntimeConfig",
  "mode": "production",
  "turnstileSiteKey": "0x4AAAAAADr9j5Z_Is8qzs20",
  "turnstileRequired": true
}

fetch

const response = await fetch("https://pvzvoyage.com/api/v1/diy/runtime-config.json");
if (!response.ok) {
  throw new Error(`PVZ: Voyage API failed: ${response.status}`);
}
const config = await response.json();
  • Do not read turnstileRequired from a publicly cached levels.json; the list response contains stable directory data only.
GET

API index

/api/v1/index.json
Best for
Launchers, bots, mirrors, proxies
Cache
Cache-Control: public, max-age=300, stale-while-revalidate=3600; CDN-Cache-Control: public, max-age=3600, stale-while-revalidate=86400
Example path
/api/v1/index.json

Field summary

Field summary
FieldTypeDescription
apiVersion string Current public API major version.
latestRelease object Latest public version, release date, and release/update/changelog links.
locales object Default locale, supported locales, and localized pages/APIs.
cache object Caching policy summary for API, download, and error responses.
families array Interface families, schemaVersion values, example paths, and documentation anchors.
documentation object Chinese/English API docs and llms.txt links.

API index fragment

{
  "schemaVersion": 1,
  "kind": "apiIndex",
  "apiVersion": "v1",
  "latestRelease": {
    "version": "0.17q",
    "releasedAt": "2026-07-01",
    "detail": {
      "path": "/api/v1/releases/latest.json",
      "url": "https://pvzvoyage.com/api/v1/releases/latest.json"
    }
  },
  "locales": {
    "default": "zh",
    "supported": [
      "zh",
      "en"
    ]
  },
  "documentation": {
    "en": {
      "path": "/en/api-docs/",
      "url": "https://pvzvoyage.com/en/api-docs/"
    },
    "zh": {
      "path": "/zh/api-docs/",
      "url": "https://pvzvoyage.com/zh/api-docs/"
    },
    "openapi": {
      "path": "/api/openapi.json",
      "url": "https://pvzvoyage.com/api/openapi.json"
    }
  },
  "families": [
    {
      "key": "release",
      "schemaVersions": [
        1,
        2
      ],
      "endpoints": [
        {
          "id": "releases-latest",
          "method": "GET",
          "pathTemplate": "/api/v1/releases/latest.json",
          "schemaVersion": 1
        },
        {
          "id": "updates-latest",
          "method": "GET",
          "pathTemplate": "/api/v1/updates/latest.json",
          "schemaVersion": 2
        }
      ]
    },
    {
      "key": "diy",
      "schemaVersions": [
        1
      ],
      "endpoints": [
        {
          "id": "diy-levels-list",
          "method": "GET · HEAD",
          "pathTemplate": "/api/v1/diy/levels.json",
          "schemaVersion": 1
        },
        {
          "id": "diy-level-detail",
          "method": "GET · HEAD",
          "pathTemplate": "/api/v1/diy/levels/{public_id}.json",
          "schemaVersion": 1
        },
        {
          "id": "diy-level-icon",
          "method": "GET · HEAD",
          "pathTemplate": "/api/v1/diy/levels/{public_id}/icon",
          "schemaVersion": 1
        },
        {
          "id": "diy-runtime-config",
          "method": "GET · HEAD",
          "pathTemplate": "/api/v1/diy/runtime-config.json",
          "schemaVersion": 1
        },
        {
          "id": "diy-level-upload",
          "method": "POST",
          "pathTemplate": "/api/v1/diy/levels",
          "schemaVersion": 1
        },
        {
          "id": "diy-owner-session",
          "method": "POST",
          "pathTemplate": "/api/v1/diy/levels/{public_id}/owner/session",
          "schemaVersion": 1
        },
        {
          "id": "diy-owner-status",
          "method": "POST",
          "pathTemplate": "/api/v1/diy/levels/{public_id}/owner/status",
          "schemaVersion": 1
        },
        {
          "id": "diy-owner-edit",
          "method": "POST",
          "pathTemplate": "/api/v1/diy/levels/{public_id}/owner/edit",
          "schemaVersion": 1
        },
        {
          "id": "diy-owner-remove",
          "method": "POST",
          "pathTemplate": "/api/v1/diy/levels/{public_id}/owner/remove",
          "schemaVersion": 1
        },
        {
          "id": "diy-owner-update",
          "method": "POST",
          "pathTemplate": "/api/v1/diy/levels/{public_id}/owner/update",
          "schemaVersion": 1
        }
      ]
    }
  ]
}

curl

curl -fsSL \
  -A "PVZVoyage/0.17q (+https://pvzvoyage.com/)" \
  https://pvzvoyage.com/api/v1/index.json

fetch

// Server-side fetch example. Browser fetch sends a browser User-Agent automatically.
const response = await fetch("https://pvzvoyage.com/api/v1/index.json", {
  headers: {
    "User-Agent": "PVZVoyage/0.17q (+https://pvzvoyage.com/)",
  },
});
if (!response.ok) {
  throw new Error(`PVZ: Voyage API failed: ${response.status}`);
}
const apiIndex = await response.json();
  • Clients can read the index first, then choose release, content, search/news, download, or RSS interfaces by use case.
  • The index lists stable entry points and templates, but does not expand every encyclopedia slug; read the locale index JSON when concrete entries are needed.
GET

Latest release detail

/api/v1/releases/latest.json
Best for
Launchers, mirrors, download bots
Cache
Cache-Control: public, max-age=300, stale-while-revalidate=3600; CDN-Cache-Control: public, max-age=3600, stale-while-revalidate=86400
Example path
/api/v1/releases/latest.json

Field summary

Field summary
FieldTypeDescription
version string Current release version.
platforms string[] Available platform keys.
builds object Build details keyed by platform.
builds.*.sha256 string SHA-256 digest for the binary.
feeds object English and Chinese RSS feed links.
urls.canonical Link Version-pinned detail URL.

Trimmed release detail

{
  "schemaVersion": 1,
  "kind": "release",
  "version": "0.17q",
  "releasedAt": "2026-07-01",
  "latest": true,
  "primaryPlatform": "windows",
  "platforms": [
    "windows",
    "windows-zip",
    "windows-x86",
    "windows-x86-zip",
    "android"
  ],
  "urls": {
    "self": {
      "path": "/api/v1/releases/latest.json",
      "url": "https://pvzvoyage.com/api/v1/releases/latest.json"
    },
    "canonical": {
      "path": "/api/v1/releases/0.17q.json",
      "url": "https://pvzvoyage.com/api/v1/releases/0.17q.json"
    }
  },
  "builds": {
    "windows": {
      "platformKey": "windows",
      "platform": {
        "zh": "Windows 64 位(自解压)",
        "en": "Windows x64 (self-extracting EXE)"
      },
      "path": "/download/0.17q/windows",
      "url": "https://pvzvoyage.com/download/0.17q/windows",
      "aliasPath": "/download/0.17q/windows.exe",
      "filename": "PVZ-Voyage-0.17q-Windows-x64.exe",
      "size": 151519712,
      "sizeMiB": 144.5,
      "sha256": "126f641200dba871499c929f20fe2bd92ddbc8f8b051a5f9809764d7a493af57",
      "digest": {
        "algorithm": "SHA-256",
        "hex": "126f641200dba871499c929f20fe2bd92ddbc8f8b051a5f9809764d7a493af57"
      },
      "mime": "application/vnd.microsoft.portable-executable",
      "extension": ".exe",
      "minOs": {
        "name": "Windows 7",
        "build": 7600,
        "label": "Windows 7+"
      },
      "note": "Example shows the Windows x64 build only; the real payload includes every platform."
    }
  }
}

curl

curl -fsSL \
  -A "PVZVoyage/0.17q (+https://pvzvoyage.com/)" \
  https://pvzvoyage.com/api/v1/releases/latest.json

fetch

// Server-side fetch example. Browser fetch sends a browser User-Agent automatically.
const response = await fetch("https://pvzvoyage.com/api/v1/releases/latest.json", {
  headers: {
    "User-Agent": "PVZVoyage/0.17q (+https://pvzvoyage.com/)",
  },
});
if (!response.ok) {
  throw new Error(`PVZ: Voyage API failed: ${response.status}`);
}
const release = await response.json();
  • Store urls.canonical when you need immutable release data.
  • Clients should choose a platform from builds instead of hardcoding download URLs.
GET

Auto-update manifest

/api/v1/updates/latest.json
Best for
Updaters and launchers
Cache
Cache-Control: public, max-age=300, stale-while-revalidate=3600; CDN-Cache-Control: public, max-age=3600, stale-while-revalidate=86400
Example path
/api/v1/updates/latest.json

Field summary

Field summary
FieldTypeDescription
url string Primary build download URL kept for v1 consumers.
sha256 string Primary build checksum.
builds object All platform builds.
minOs object Minimum OS requirement.
changelog object Human changelog pages and JSON changelog link.

Updater example

{
  "schemaVersion": 2,
  "version": "0.17q",
  "releasedAt": "2026-07-01",
  "url": "https://pvzvoyage.com/download/0.17q/windows",
  "sha256": "126f641200dba871499c929f20fe2bd92ddbc8f8b051a5f9809764d7a493af57",
  "size": 151519712,
  "filename": "PVZ-Voyage-0.17q-Windows-x64.exe",
  "minOs": {
    "name": "Windows 7",
    "build": 7600
  },
  "builds": {
    "windows": {
      "url": "https://pvzvoyage.com/download/0.17q/windows",
      "sha256": "126f641200dba871499c929f20fe2bd92ddbc8f8b051a5f9809764d7a493af57",
      "size": 151519712,
      "filename": "PVZ-Voyage-0.17q-Windows-x64.exe",
      "minOs": {
        "name": "Windows 7",
        "build": 7600
      }
    }
  }
}

curl

curl -fsSL \
  -A "PVZVoyage/0.17q (+https://pvzvoyage.com/)" \
  https://pvzvoyage.com/api/v1/updates/latest.json

fetch

// Server-side fetch example. Browser fetch sends a browser User-Agent automatically.
const response = await fetch("https://pvzvoyage.com/api/v1/updates/latest.json", {
  headers: {
    "User-Agent": "PVZVoyage/0.17q (+https://pvzvoyage.com/)",
  },
});
if (!response.ok) {
  throw new Error(`PVZ: Voyage API failed: ${response.status}`);
}
const manifest = await response.json();
  • If you control the client, prefer release detail; it is more complete than the updater manifest.
  • Top-level fields are a compatibility layer, not the only downloadable platform.
GET

Release index

/api/v1/releases.json
Best for
Release lists and historical navigation
Cache
Cache-Control: public, max-age=300, stale-while-revalidate=3600; CDN-Cache-Control: public, max-age=3600, stale-while-revalidate=86400
Example path
/api/v1/releases.json

Field summary

Field summary
FieldTypeDescription
latest string Latest version.
releases array Published releases.
releases[].detail string Version-pinned detail API.
releases[].latestDetail string | null Latest detail alias for the current version.

Release index example

{
  "schemaVersion": 1,
  "latest": "0.17q",
  "releases": [
    {
      "version": "0.17q",
      "releasedAt": "2026-07-01",
      "url": "https://pvzvoyage.com/download/0.17q/windows",
      "sha256": "126f641200dba871499c929f20fe2bd92ddbc8f8b051a5f9809764d7a493af57",
      "detail": "https://pvzvoyage.com/api/v1/releases/0.17q.json",
      "latestDetail": "https://pvzvoyage.com/api/v1/releases/latest.json"
    }
  ]
}

curl

curl -fsSL \
  -A "PVZVoyage/0.17q (+https://pvzvoyage.com/)" \
  https://pvzvoyage.com/api/v1/releases.json

fetch

// Server-side fetch example. Browser fetch sends a browser User-Agent automatically.
const response = await fetch("https://pvzvoyage.com/api/v1/releases.json", {
  headers: {
    "User-Agent": "PVZVoyage/0.17q (+https://pvzvoyage.com/)",
  },
});
if (!response.ok) {
  throw new Error(`PVZ: Voyage API failed: ${response.status}`);
}
const index = await response.json();
  • Use this when you only need a version list; follow detail for platform builds.
GET

Structured changelog

/api/v1/changelog/{version}.json
Best for
Announcement bots, update pages, community tools
Cache
Cache-Control: public, max-age=300, stale-while-revalidate=604800; CDN-Cache-Control: public, max-age=86400, stale-while-revalidate=604800
Example path
/api/v1/changelog/0.17q.json

Field summary

Field summary
FieldTypeDescription
version string Version represented by the changelog.
locales.zh object Chinese changelog.
locales.en object English changelog.
sections array Headings, paragraphs, subsections, and items.

Changelog shape example

{
  "schemaVersion": 1,
  "version": "0.17q",
  "releasedAt": "2026-07-01",
  "locales": {
    "en": {
      "title": "PVZ: Voyage 0.17q Changelog",
      "sections": [
        {
          "title": "Highlights",
          "paragraphs": [
            "..."
          ],
          "sections": []
        }
      ]
    }
  }
}

curl

curl -fsSL \
  -A "PVZVoyage/0.17q (+https://pvzvoyage.com/)" \
  https://pvzvoyage.com/api/v1/changelog/0.17q.json

fetch

// Server-side fetch example. Browser fetch sends a browser User-Agent automatically.
const response = await fetch("https://pvzvoyage.com/api/v1/changelog/0.17q.json", {
  headers: {
    "User-Agent": "PVZVoyage/0.17q (+https://pvzvoyage.com/)",
  },
});
if (!response.ok) {
  throw new Error(`PVZ: Voyage API failed: ${response.status}`);
}
const changelog = await response.json();
  • The site currently publishes structured JSON for the current version.
GET

Field Guide index

/api/v1/encyclopedia/{locale}/index.json
Best for
Field Guide browsers, wiki tools, search clients
Cache
Cache-Control: public, max-age=300, stale-while-revalidate=21600; CDN-Cache-Control: public, max-age=21600, stale-while-revalidate=86400
Example path
/api/v1/encyclopedia/en/index.json

Field summary

Field summary
FieldTypeDescription
locale 'zh' | 'en' Current locale.
tabs array Plant, zombie, and boss categories with counts.
entries array Entry summaries, page URLs, API URLs, and portraits.
alternate object Other-locale page and API.

Field Guide index example

{
  "schemaVersion": 1,
  "kind": "encyclopediaIndex",
  "locale": "en",
  "title": "Field Guide",
  "counts": {
    "total": 48,
    "byKind": {
      "plant": 22,
      "zombie": 16,
      "boss": 10
    }
  },
  "entries": [
    {
      "slug": "peashooter",
      "name": "Peashooter",
      "kind": "plant",
      "path": "/en/encyclopedia/peashooter/",
      "api": {
        "path": "/api/v1/encyclopedia/en/peashooter.json",
        "url": "https://pvzvoyage.com/api/v1/encyclopedia/en/peashooter.json"
      },
      "portrait": {
        "avif": {
          "path": "/media/encyclopedia/peashooter.avif"
        },
        "width": 1200,
        "height": 1185
      }
    }
  ]
}

curl

curl -fsSL \
  -A "PVZVoyage/0.17q (+https://pvzvoyage.com/)" \
  https://pvzvoyage.com/api/v1/encyclopedia/en/index.json

fetch

// Server-side fetch example. Browser fetch sends a browser User-Agent automatically.
const response = await fetch("https://pvzvoyage.com/api/v1/encyclopedia/en/index.json", {
  headers: {
    "User-Agent": "PVZVoyage/0.17q (+https://pvzvoyage.com/)",
  },
});
if (!response.ok) {
  throw new Error(`PVZ: Voyage API failed: ${response.status}`);
}
const fieldGuide = await response.json();
  • Slug lists come from content modules; do not hardcode them from documentation.
GET

Field Guide entry detail

/api/v1/encyclopedia/{locale}/{slug}.json
Best for
Databases, bots, Field Guide mirrors
Cache
Cache-Control: public, max-age=300, stale-while-revalidate=21600; CDN-Cache-Control: public, max-age=21600, stale-while-revalidate=86400
Example path
/api/v1/encyclopedia/en/peashooter.json

Field summary

Field summary
FieldTypeDescription
entry object Entry copy, role, abilities, and advanced mechanics.
portrait object AVIF/WebP/PNG portrait and dimensions.
navigation object Previous/next entry summaries.
alternate object | null Other-locale entry.

Entry detail example

{
  "schemaVersion": 1,
  "kind": "encyclopediaEntry",
  "locale": "en",
  "slug": "peashooter",
  "entry": {
    "slug": "peashooter",
    "kind": "plant",
    "name": "Peashooter",
    "mechanics": {
      "role": "Ranged attacker · Crossbreed base",
      "abilities": [
        "..."
      ]
    },
    "interactions": [
      {
        "target": "Wall-nut",
        "targetSlug": "wall-nut",
        "effect": "..."
      }
    ]
  },
  "portrait": {
    "png": {
      "path": "/media/encyclopedia/peashooter.png"
    },
    "width": 1200,
    "height": 1185
  }
}

curl

curl -fsSL \
  -A "PVZVoyage/0.17q (+https://pvzvoyage.com/)" \
  https://pvzvoyage.com/api/v1/encyclopedia/en/peashooter.json

fetch

// Server-side fetch example. Browser fetch sends a browser User-Agent automatically.
const response = await fetch("https://pvzvoyage.com/api/v1/encyclopedia/en/peashooter.json", {
  headers: {
    "User-Agent": "PVZVoyage/0.17q (+https://pvzvoyage.com/)",
  },
});
if (!response.ok) {
  throw new Error(`PVZ: Voyage API failed: ${response.status}`);
}
const entry = await response.json();
  • Advanced mechanics appears only on entries that have that data.
GET

World region index

/api/v1/world/{locale}/index.json
Best for
Maps, databases, region browsers
Cache
Cache-Control: public, max-age=300, stale-while-revalidate=21600; CDN-Cache-Control: public, max-age=21600, stale-while-revalidate=86400
Example path
/api/v1/world/en/index.json

Field summary

Field summary
FieldTypeDescription
regions array Region slug, number, title, summary, paragraphs, and visuals.
visual object Card/hero images and accent colors.
alternate object Other-locale world index.

World index example

{
  "schemaVersion": 1,
  "kind": "worldIndex",
  "locale": "en",
  "count": 4,
  "regions": [
    {
      "slug": "maple-falls",
      "number": "I",
      "title": "Maple Falls",
      "summary": "...",
      "path": "/en/world/maple-falls/",
      "visual": {
        "accent": "#8da66b",
        "motif": "wind / lake / stairway"
      }
    }
  ]
}

curl

curl -fsSL \
  -A "PVZVoyage/0.17q (+https://pvzvoyage.com/)" \
  https://pvzvoyage.com/api/v1/world/en/index.json

fetch

// Server-side fetch example. Browser fetch sends a browser User-Agent automatically.
const response = await fetch("https://pvzvoyage.com/api/v1/world/en/index.json", {
  headers: {
    "User-Agent": "PVZVoyage/0.17q (+https://pvzvoyage.com/)",
  },
});
if (!response.ok) {
  throw new Error(`PVZ: Voyage API failed: ${response.status}`);
}
const world = await response.json();
  • Region slugs are stable across locales; titles and prose are localized.
GET

DidYouKnow catalog

/api/v1/did-you-know/{locale}/index.json
Best for
Launchers, bots, data tools, local homepage selection
Cache
Cache-Control: public, max-age=300, stale-while-revalidate=21600; CDN-Cache-Control: public, max-age=21600, stale-while-revalidate=86400
Example path
/api/v1/did-you-know/en/index.json

Field summary

Field summary
FieldTypeDescription
counts object Total count and counts by trivia, tip, mechanic, and lore.
filters object Available contexts, kinds, spoiler levels, and tags.
selectionDefaults.homeHero object Default homepage selection rules.
items array Localized items with copy, source refs, tags, weight, and related Field Guide entries.
items[].sourceRefs array Local sources checked before publication.

DidYouKnow catalog example

{
  "schemaVersion": 1,
  "kind": "didYouKnowIndex",
  "locale": "en",
  "counts": {
    "total": 36,
    "byKind": {
      "trivia": 2,
      "tip": 22,
      "mechanic": 11,
      "lore": 1
    }
  },
  "selectionDefaults": {
    "homeHero": {
      "allowedSpoilerLevels": [
        "none",
        "mild",
        "mechanics"
      ],
      "preferredRelatedSlugs": [
        "sunflower",
        "peashooter"
      ],
      "maxRecentIds": 12
    }
  },
  "items": [
    {
      "id": "sunflower-match-inheritance",
      "kind": "mechanic",
      "text": "Sunflower matching is no longer directional, and CocoBottle Shine can pass its large-sun charge into the kept plant.",
      "spoilerLevel": "mechanics",
      "contexts": [
        "homeHero",
        "encyclopedia",
        "api"
      ],
      "relatedEntries": [
        {
          "slug": "sunflower",
          "name": "Sunflower",
          "path": "/en/encyclopedia/sunflower/"
        }
      ]
    }
  ]
}

curl

curl -fsSL \
  -A "PVZVoyage/0.17q (+https://pvzvoyage.com/)" \
  https://pvzvoyage.com/api/v1/did-you-know/en/index.json

fetch

// Server-side fetch example. Browser fetch sends a browser User-Agent automatically.
const response = await fetch("https://pvzvoyage.com/api/v1/did-you-know/en/index.json", {
  headers: {
    "User-Agent": "PVZVoyage/0.17q (+https://pvzvoyage.com/)",
  },
});
if (!response.ok) {
  throw new Error(`PVZ: Voyage API failed: ${response.status}`);
}
const didYouKnow = await response.json();
  • Items are maintained in repository content files and validated at build time for IDs, locales, slugs, length, tags, and spoiler levels.
  • Use random.json for a single result; read the full catalog when you want to cache or filter locally.
GET · HEAD

Random DidYouKnow selection

/api/v1/did-you-know/{locale}/random.json
Best for
Launcher welcome panes, bots, lightweight tools
Cache
Explicit seed uses Cache-Control: public, max-age=300, stale-while-revalidate=86400; no seed uses a UTC hour bucket and public, max-age=60, stale-while-revalidate=300. Both emit CDN-Cache-Control and use shared-cache normalized queries.
Example path
/api/v1/did-you-know/en/random.json?context=homeHero&entry=sunflower&seed=session-42

Field summary

Field summary
FieldTypeDescription
context query homeHero, encyclopedia, api, or any.
kind query Comma-separated trivia, tip, mechanic, or lore.
entry query Related Field Guide slug; preferred by default and required with strictEntry=1.
strictEntry query 0 or 1; when set to 1, entry is required and selection is limited to items related to that entry.
tags query Comma-separated URL-safe tags, at most 12; matches any supplied tag.
spoilerMax query Maximum spoiler level; defaults to mechanics for homeHero and lateGame otherwise.
exclude query Comma-separated item IDs; at most 20 are accepted.
seed query Up to 128 characters; when omitted, the current UTC hour bucket is used.
X-PVZV-DidYouKnow-Cache response header HIT or MISS, useful for verifying the random-selection cache.
selection object Seed, pool size, excluded count, and filters actually used.
item object Selected DidYouKnow item.

Random selection example

{
  "schemaVersion": 1,
  "kind": "didYouKnowSelection",
  "locale": "en",
  "selection": {
    "strategy": "weighted-stable-random",
    "seed": "session-42",
    "context": "homeHero",
    "poolSize": 31,
    "excludedCount": 0,
    "filters": {
      "entry": "sunflower",
      "strictEntry": false,
      "spoilerMax": "mechanics"
    }
  },
  "item": {
    "id": "sunflower-match-inheritance",
    "kind": "mechanic",
    "text": "Sunflower matching is no longer directional, and CocoBottle Shine can pass its large-sun charge into the kept plant.",
    "cta": {
      "path": "/en/encyclopedia/sunflower/",
      "url": "https://pvzvoyage.com/en/encyclopedia/sunflower/"
    }
  }
}

curl

curl -fsSL \
  -A "PVZVoyage/0.17q (+https://pvzvoyage.com/)" \
  "https://pvzvoyage.com/api/v1/did-you-know/en/random.json?context=homeHero&entry=sunflower&seed=session-42"

fetch

// Server-side fetch example. Browser fetch sends a browser User-Agent automatically.
const response = await fetch("https://pvzvoyage.com/api/v1/did-you-know/en/random.json?context=homeHero&entry=sunflower&seed=session-42", {
  headers: {
    "User-Agent": "PVZVoyage/0.17q (+https://pvzvoyage.com/)",
  },
});
if (!response.ok) {
  throw new Error(`PVZ: Voyage API failed: ${response.status}`);
}
const selection = await response.json();
  • The same catalog, query, and seed produce a stable result; catalog changes may select a different item.
  • Query parameters are normalized by effective semantics before entering the shared cache; tag order does not create a separate selection cache.
  • Bad parameters return JSON 400; no matching item returns JSON 404 with code no_matching_item.
GET · HEAD

DIY level list

/api/v1/diy/levels.json
Best for
Level libraries, launchers, game clients, data tools
Cache
Cache-Control: public, max-age=60, s-maxage=300
Example path
/api/v1/diy/levels.json?sort=hot&tag=conveyor&limit=20

Field summary

Field summary
FieldTypeDescription
q · optional query Search public title, author, or description; up to 60 characters.
ids · optional query Comma-separated public level IDs, at most 50; useful for local favorite lookup.
version · optional query Exact game-version filter, such as 0.17q.
difficulty · optional query Display/design difficulty integer 0..9; this is an independent display scale, not the game environment difficulty.
difficultyMin / difficultyMax · optional query Display/design difficulty range, integer 0..9.
background · optional query Background ID.
wavesMin / wavesMax · optional query Max-wave range, 1..100.
subLevelsMin / subLevelsMax · optional query Sub-level count range, 1..8.
downloadsMin / downloadsMax · optional query Approximate download-count range, using non-negative integers.
tag · optional query Repeatable or comma-separated; multiple tags are matched together.
sort · optional query latest, hot, name, downloads, or difficulty; defaults to latest.
cursor · optional query Opaque pagination cursor returned by the previous page.
limit · optional query 1..50, default 20.
mode string Public enabled-state marker for this interface.
levels array Public level summaries.
levels[].titleZh string Chinese public title.
levels[].titleEn string English public title.
levels[].descriptionZh string Chinese public description.
levels[].descriptionEn string English public description.
levels[].icon object | null Public level icon link and image metadata; null when no icon was uploaded.
levels[].download Link GDF download link.
pagination.nextCursor string | null Cursor for the next page; null means there is no next page.
pagination.total number | null Total for the current filter set, null when unavailable.

DIY level list example

{
  "schemaVersion": 1,
  "kind": "diyLevelList",
  "mode": "production",
  "levels": [
    {
      "schemaVersion": 1,
      "id": "sample-level-id",
      "title": "Sample Conveyor Trial",
      "description": "Short public description shown in the library.",
      "titleZh": "传送带试炼",
      "titleEn": "Sample Conveyor Trial",
      "descriptionZh": "在关卡库中展示的中文简介。",
      "descriptionEn": "Short public description shown in the library.",
      "author": "Example Creator",
      "gameVersion": "0.17q",
      "difficulty": 3,
      "way": 0,
      "waves": 10,
      "background": 1,
      "subLevelCount": 1,
      "modeTags": [
        "conveyor"
      ],
      "sizeBytes": 20480,
      "sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
      "icon": {
        "path": "/api/v1/diy/levels/sample-level-id/icon",
        "url": "https://pvzvoyage.com/api/v1/diy/levels/sample-level-id/icon",
        "contentType": "image/png",
        "width": 96,
        "height": 96,
        "sizeBytes": 8192,
        "sha256": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
      },
      "downloadCountApprox": 42,
      "approvedAt": "2026-06-27T01:00:00.000Z",
      "detail": {
        "path": "/api/v1/diy/levels/sample-level-id.json",
        "url": "https://pvzvoyage.com/api/v1/diy/levels/sample-level-id.json"
      },
      "download": {
        "path": "/api/v1/diy/levels/sample-level-id/download.gdf",
        "url": "https://pvzvoyage.com/api/v1/diy/levels/sample-level-id/download.gdf"
      }
    }
  ],
  "pagination": {
    "nextCursor": "opaque-cursor",
    "total": 128
  }
}

curl

curl -fsSL \
  -A "PVZVoyage/0.17q (+https://pvzvoyage.com/)" \
  "https://pvzvoyage.com/api/v1/diy/levels.json?sort=hot&tag=conveyor&limit=20"

fetch

// Server-side fetch example. Browser fetch sends a browser User-Agent automatically.
const response = await fetch("https://pvzvoyage.com/api/v1/diy/levels.json?sort=hot&tag=conveyor&limit=20", {
  headers: {
    "User-Agent": "PVZVoyage/0.17q (+https://pvzvoyage.com/)",
  },
});
if (!response.ok) {
  throw new Error(`PVZ: Voyage API failed: ${response.status}`);
}
const levels = await response.json();
  • The list only returns levels that are publicly visible; ids batch lookup omits missing or unavailable IDs.
  • Pagination cursors are opaque strings; keep the same sort and filters when following a cursor.
  • Request-level Turnstile configuration for uploads is served by /api/v1/diy/runtime-config.json; the list response contains stable directory data only.
  • Mode tags come from a controlled vocabulary: conveyor, no-glory, sun-limit, unlimited-sun, no-mower, no-shovel, sequential-spawn, endless, boss-fight, puzzle, speedrun, beginner, extreme.
GET · HEAD

DIY level detail

/api/v1/diy/levels/{public_id}.json
Best for
Level detail pages, game clients, data tools
Cache
Cache-Control: public, max-age=300, s-maxage=3600
Example path
/api/v1/diy/levels/sample-level-id.json

Field summary

Field summary
FieldTypeDescription
public_id path Public level ID.
level.id string Public level ID.
level.title string Compatibility public-title projection; localized clients should use titleZh/titleEn.
level.description string Compatibility public-description projection; localized clients should use descriptionZh/descriptionEn.
level.titleZh string Chinese public title.
level.titleEn string English public title.
level.descriptionZh string Chinese public description.
level.descriptionEn string English public description.
level.author string Author read from the GDF.
level.gameVersion string | null Game version declared by the GDF.
level.difficulty number | null Level display/design difficulty value; defaults to the GDF-parsed value and can be manually labeled at upload or in management.
level.environmentDifficulty number | null Game-internal environment difficulty, usually 0..4 with 5..7 recognized.
level.waves number | null Maximum wave count.
level.background number | null Background ID for the first sub-level.
level.subLevelCount number Number of sub-levels.
level.modeTags string[] Public mode tags.
level.sizeBytes number GDF file size.
level.sha256 string GDF file SHA-256.
level.icon object | null Level icon link and PNG/JPEG/WebP image metadata; null when no icon was uploaded.
level.downloadCountApprox number Approximate download count for display.
level.gdfName · optional string | null Name stored in the GDF.
level.gdfDescription · optional string | null Description stored in the GDF.
level.seed · optional number | null GDF random seed.
level.crcValid boolean GDF CRC validation result.
level.subLevels · optional array | null Multi-sub-level summary.
level.subLevels[].difficulty · optional number | null Display/design difficulty for that sub-level.
level.summaryJson object GDF information summary object extracted and normalized from the GDF JSON payload. It retains the name, author, version, difficulty, waves, background, sub-levels, and zombie overview; it is not the original level JSON that can be packed back into a .gdf file.

DIY level detail example

{
  "schemaVersion": 1,
  "kind": "diyLevelDetail",
  "mode": "production",
  "level": {
    "schemaVersion": 1,
    "id": "sample-level-id",
    "title": "Sample Conveyor Trial",
    "description": "Short public description shown in the library.",
    "titleZh": "传送带试炼",
    "titleEn": "Sample Conveyor Trial",
    "descriptionZh": "在关卡库中展示的中文简介。",
    "descriptionEn": "Short public description shown in the library.",
    "author": "Example Creator",
    "gameVersion": "0.17q",
    "difficulty": 3,
    "environmentDifficulty": 2,
    "way": 0,
    "waves": 10,
    "background": 1,
    "subLevelCount": 2,
    "modeTags": [
      "conveyor"
    ],
    "sizeBytes": 20480,
    "sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
    "icon": {
      "path": "/api/v1/diy/levels/sample-level-id/icon",
      "url": "https://pvzvoyage.com/api/v1/diy/levels/sample-level-id/icon",
      "contentType": "image/png",
      "width": 96,
      "height": 96,
      "sizeBytes": 8192,
      "sha256": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
    },
    "downloadCountApprox": 42,
    "approvedAt": "2026-06-27T01:00:00.000Z",
    "detail": {
      "path": "/api/v1/diy/levels/sample-level-id.json",
      "url": "https://pvzvoyage.com/api/v1/diy/levels/sample-level-id.json"
    },
    "download": {
      "path": "/api/v1/diy/levels/sample-level-id/download.gdf",
      "url": "https://pvzvoyage.com/api/v1/diy/levels/sample-level-id/download.gdf"
    },
    "gdfName": "Sample Conveyor Trial",
    "gdfDescription": "GDF-authored description if present.",
    "seed": 123,
    "crcValid": true,
    "subLevels": [
      {
        "index": 0,
        "difficulty": 2,
        "waves": 8,
        "background": 1,
        "zombieSummary": [
          "Zombie",
          "ConeheadZombie"
        ]
      },
      {
        "index": 1,
        "difficulty": 3,
        "waves": 10,
        "background": 3,
        "zombieSummary": [
          "BucketheadZombie",
          "FlagZombie"
        ]
      }
    ],
    "summaryJson": {
      "gdf_name": "Sample Conveyor Trial",
      "extracted_author": "Example Creator",
      "gdf_description": "GDF-authored description if present.",
      "game_version": "0.17q",
      "difficulty": 3,
      "environment_difficulty": 2,
      "way": 0,
      "waves": 10,
      "waves_range": "8-10",
      "background": 1,
      "sub_level_count": 2,
      "sub_levels": [
        {
          "index": 0,
          "waves": 8,
          "background": 1,
          "difficulty": 2,
          "zombie_summary": [
            "Zombie",
            "ConeheadZombie"
          ]
        },
        {
          "index": 1,
          "waves": 10,
          "background": 3,
          "difficulty": 3,
          "zombie_summary": [
            "BucketheadZombie",
            "FlagZombie"
          ]
        }
      ]
    }
  }
}

curl

curl -fsSL \
  -A "PVZVoyage/0.17q (+https://pvzvoyage.com/)" \
  https://pvzvoyage.com/api/v1/diy/levels/sample-level-id.json

fetch

// Server-side fetch example. Browser fetch sends a browser User-Agent automatically.
const response = await fetch("https://pvzvoyage.com/api/v1/diy/levels/sample-level-id.json", {
  headers: {
    "User-Agent": "PVZVoyage/0.17q (+https://pvzvoyage.com/)",
  },
});
if (!response.ok) {
  throw new Error(`PVZ: Voyage API failed: ${response.status}`);
}
const level = await response.json();
  • Unavailable or missing IDs return a generic JSON 404; clients should not infer level state from that response.
  • Use level.download.url for the GDF and verify integrity with level.sha256.
  • level.summaryJson is the site-extracted GDF information summary. Download and unpack the .gdf file when the original level JSON is needed.
  • level.difficulty is the display/design difficulty; level.environmentDifficulty is the pack-level Dif game environment difficulty.
GET · HEAD

DIY GDF download

/api/v1/diy/levels/{public_id}/download.gdf
Best for
Game clients, launchers, level managers
Cache
Cache-Control: public, max-age=300, stale-while-revalidate=300
Example path
/api/v1/diy/levels/sample-level-id/download.gdf

Field summary

Field summary
FieldTypeDescription
public_id path Public level ID.
Content-Type header application/octet-stream.
Content-Disposition header Attachment filename, usually based on the public title.
Content-Length header GDF file size.
ETag header File identifier corresponding to sha256 in detail JSON.
Cache-Control header Cache policy for public versioned files.

HEAD response example

HTTP/2 200
content-type: application/octet-stream
content-disposition: attachment; filename="Sample Conveyor Trial.gdf"
content-length: 20480
etag: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
cache-control: public, max-age=300, stale-while-revalidate=300

HEAD

curl -I \
  -A "PVZVoyage/0.17q (+https://pvzvoyage.com/)" \
  https://pvzvoyage.com/api/v1/diy/levels/sample-level-id/download.gdf

GET

curl -fsSL \
  -A "PVZVoyage/0.17q (+https://pvzvoyage.com/)" \
  -o "Sample Conveyor Trial.gdf" \
  https://pvzvoyage.com/api/v1/diy/levels/sample-level-id/download.gdf
  • Read detail JSON before download when you need display metadata and sha256.
  • Unavailable or missing IDs return a generic JSON 404.
GET · HEAD

DIY level icon

/api/v1/diy/levels/{public_id}/icon
Best for
Level libraries, launchers, game clients
Cache
Cache-Control: public, max-age=300
Example path
/api/v1/diy/levels/sample-level-id/icon

Field summary

Field summary
FieldTypeDescription
public_id path Public level ID.
Content-Type header image/png, image/jpeg, or image/webp.
Content-Disposition header inline.
Content-Length header Icon file size.
ETag header Icon SHA-256 file identifier.
X-PVZV-DIY-Icon-Width header Icon width in pixels.
X-PVZV-DIY-Icon-Height header Icon height in pixels.
Cache-Control header Public icon cache policy.

HEAD response example

HTTP/2 200
content-type: image/png
content-disposition: inline
content-length: 8192
etag: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
x-pvzv-diy-icon-width: 96
x-pvzv-diy-icon-height: 96
cache-control: public, max-age=300

HEAD

curl -I \
  -A "PVZVoyage/0.17q (+https://pvzvoyage.com/)" \
  https://pvzvoyage.com/api/v1/diy/levels/sample-level-id/icon

GET

curl -fsSL \
  -A "PVZVoyage/0.17q (+https://pvzvoyage.com/)" \
  -o "sample-level-icon.png" \
  https://pvzvoyage.com/api/v1/diy/levels/sample-level-id/icon
  • Only approved levels with an uploaded icon return an image; other cases return the generic JSON 404.
  • Clients should usually use level.icon.path or level.icon.url instead of constructing the path manually.
POST

Submit a DIY level

/api/v1/diy/levels
Best for
Site upload widget, game clients
Cache
Cache-Control: private, no-store
Example path
/api/v1/diy/levels

Field summary

Field summary
FieldTypeDescription
file multipart field Required .gdf file, up to 256 KiB.
icon · optional multipart field Optional level icon. PNG, JPEG, or WebP only, up to 96 KiB, 32..512 px; the full multipart request must stay below 480 KiB.
titleZh · optional multipart field Chinese public title, 2..80 characters when provided.
titleEn · optional multipart field English public title, 2..80 characters when provided.
descriptionZh · optional multipart field Chinese public description, up to 1500 characters.
descriptionEn · optional multipart field English public description, up to 1500 characters.
modeTags · optional multipart field Comma-separated or repeated field, at most 5 controlled tags.
turnstileToken · optional multipart field Verification token supplied by the browser upload widget when needed.
status string pending after a successful submission.
level object New level ID, title, author, sha256, and submission time.
ownerCredential object Owner credential filename, text, and secret; returned once, so keep it safely.

Submission success example

{
  "schemaVersion": 1,
  "status": "pending",
  "level": {
    "id": "sample-level-id",
    "title": "Sample Conveyor Trial",
    "author": "Example Creator",
    "sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
    "createdAt": "2026-06-27T01:00:00.000Z"
  },
  "ownerCredential": {
    "filename": "pvzvoyage-diy-sample-level-id-owner.txt",
    "text": "PVZ: Voyage DIY owner credential\nLevel ID: sample-level-id\nSecret: example_owner_secret",
    "secret": "example_owner_secret"
  }
}

curl

curl -fsSL \
  -A "PVZVoyage/0.17q (+https://pvzvoyage.com/)" \
  -F "file=@level.gdf;type=application/octet-stream" \
  -F "icon=@icon.png;type=image/png" \
  -F "titleZh=传送带试炼" \
  -F "titleEn=Sample Conveyor Trial" \
  -F "descriptionZh=中文公开简介。" \
  -F "descriptionEn=Short public description." \
  -F "modeTags=conveyor,puzzle" \
  https://pvzvoyage.com/api/v1/diy/levels

fetch

const formData = new FormData();
formData.set("file", file, file.name);
if (iconFile) formData.set("icon", iconFile, iconFile.name);
formData.set("titleZh", "传送带试炼");
formData.set("titleEn", "Sample Conveyor Trial");
formData.set("descriptionZh", "中文公开简介。");
formData.set("descriptionEn", "Short public description.");
formData.set("modeTags", "conveyor,puzzle");
if (turnstileToken) formData.set("turnstileToken", turnstileToken);

const response = await fetch("https://pvzvoyage.com/api/v1/diy/levels", {
  method: "POST",
  body: formData,
});
const result = await response.json();
if (!response.ok) {
  throw new Error(result?.error?.message ?? "DIY upload failed");
}
  • A successful response does not mean the level is public yet; clients should show the pending state and save ownerCredential.
  • The GDF must contain valid author, CRC, and level data; at most 8 sub-levels are accepted, with 1..100 waves per sub-level.
  • At least one of titleZh and titleEn is required; moderators can add the other language during review. Legacy clients may still send title/description, which are stored for both languages.
  • A successful response is 202 Accepted and exposes the formal lifecycle through Location, owner.statusUrl, owner.editUrl, owner.removeUrl, and owner.updateUrl. Owner APIs accept the Bearer owner secret; the website continues to use an HttpOnly session.
POST

DIY owner: session

/api/v1/diy/levels/{public_id}/owner/session
Best for
Game clients and creator tools
Cache
Cache-Control: private, no-store
Example path
/api/v1/diy/levels/sample-level-id/owner/session

Field summary

Field summary
FieldTypeDescription
secret string Owner secret returned once at upload.

Owner API response

{
  "schemaVersion": 1,
  "ok": true,
  "expiresAt": "2026-06-27T02:00:00.000Z"
}

fetch

const response = await fetch("https://pvzvoyage.com/api/v1/diy/levels/sample-level-id/owner/session", {
  method: "POST",
  headers: { "Content-Type": "application/json", "X-PVZV-CSRF": "1" },
  body: JSON.stringify({ secret: "<owner-secret>" }),
});
const result = await response.json();
  • The session endpoint is a same-origin website flow; general clients can use the Bearer secret directly on other owner actions.
POST

DIY owner: status

/api/v1/diy/levels/{public_id}/owner/status
Best for
Game clients and creator tools
Cache
Cache-Control: private, no-store
Example path
/api/v1/diy/levels/sample-level-id/owner/status

Field summary

Field summary
FieldTypeDescription
Authorization · optional header Bearer owner secret; the website may use an owner session.
X-PVZV-CSRF · optional header Required only for the Cookie browser flow; not required for Bearer clients.
body · optional application/json Edit fields or an empty object.

Owner API response

{
  "schemaVersion": 1,
  "level": {
    "id": "sample-level-id",
    "status": "pending"
  }
}

fetch

const response = await fetch("https://pvzvoyage.com/api/v1/diy/levels/sample-level-id/owner/status", {
  method: "POST",
  headers: { Authorization: "Bearer <owner-secret>", "Content-Type": "application/json" },
  body: JSON.stringify({}),
});
const result = await response.json();
  • Bearer clients do not need Origin, Sec-Fetch-Site, or a CSRF header; Cookie browser flows remain protected by same-origin and CSRF checks.
POST

DIY owner: edit

/api/v1/diy/levels/{public_id}/owner/edit
Best for
Game clients and creator tools
Cache
Cache-Control: private, no-store
Example path
/api/v1/diy/levels/sample-level-id/owner/edit

Field summary

Field summary
FieldTypeDescription
Authorization · optional header Bearer owner secret; the website may use an owner session.
X-PVZV-CSRF · optional header Required only for the Cookie browser flow; not required for Bearer clients.
body application/json Edit fields or an empty object.

Owner API response

{
  "schemaVersion": 1,
  "ok": true,
  "status": "updated"
}

fetch

const response = await fetch("https://pvzvoyage.com/api/v1/diy/levels/sample-level-id/owner/edit", {
  method: "POST",
  headers: { Authorization: "Bearer <owner-secret>", "Content-Type": "application/json" },
  body: JSON.stringify({}),
});
const result = await response.json();
  • Bearer clients do not need Origin, Sec-Fetch-Site, or a CSRF header; Cookie browser flows remain protected by same-origin and CSRF checks.
POST

DIY owner: remove

/api/v1/diy/levels/{public_id}/owner/remove
Best for
Game clients and creator tools
Cache
Cache-Control: private, no-store
Example path
/api/v1/diy/levels/sample-level-id/owner/remove

Field summary

Field summary
FieldTypeDescription
Authorization · optional header Bearer owner secret; the website may use an owner session.
X-PVZV-CSRF · optional header Required only for the Cookie browser flow; not required for Bearer clients.
body · optional application/json Edit fields or an empty object.

Owner API response

{
  "schemaVersion": 1,
  "ok": true,
  "status": "removed"
}

fetch

const response = await fetch("https://pvzvoyage.com/api/v1/diy/levels/sample-level-id/owner/remove", {
  method: "POST",
  headers: { Authorization: "Bearer <owner-secret>", "Content-Type": "application/json" },
  body: JSON.stringify({}),
});
const result = await response.json();
  • Bearer clients do not need Origin, Sec-Fetch-Site, or a CSRF header; Cookie browser flows remain protected by same-origin and CSRF checks.
POST

DIY owner: update

/api/v1/diy/levels/{public_id}/owner/update
Best for
Game clients and creator tools
Cache
Cache-Control: private, no-store
Example path
/api/v1/diy/levels/sample-level-id/owner/update

Field summary

Field summary
FieldTypeDescription
Authorization · optional header Bearer owner secret; the website may use an owner session.
X-PVZV-CSRF · optional header Required only for the Cookie browser flow; not required for Bearer clients.
body multipart/form-data Complete .gdf with optional metadata.

Owner API response

{
  "schemaVersion": 1,
  "ok": true,
  "status": "pending_update"
}

fetch

const response = await fetch("https://pvzvoyage.com/api/v1/diy/levels/sample-level-id/owner/update", {
  method: "POST",
  headers: { Authorization: "Bearer <owner-secret>", "Content-Type": "application/json" },
  body: JSON.stringify({}),
});
const result = await response.json();
  • Bearer clients do not need Origin, Sec-Fetch-Site, or a CSRF header; Cookie browser flows remain protected by same-origin and CSRF checks.
GET

Stable search corpus

/api/v1/search/{locale}.json
Best for
Bots, external search, lightweight mirrors
Cache
Cache-Control: public, max-age=300, stale-while-revalidate=21600; CDN-Cache-Control: public, max-age=21600, stale-while-revalidate=86400
Example path
/api/v1/search/en.json

Field summary

Field summary
FieldTypeDescription
documentCount number Number of documents.
source object Source file note for the internal search index.
documents array Normalized documents.
documents[].kind string nav, changelog, world, encyclopedia, mechanics, channel, faq, and more.

Search document example

{
  "schemaVersion": 1,
  "kind": "searchIndex",
  "locale": "en",
  "documentCount": 100,
  "documents": [
    {
      "id": "encyclopedia:en:peashooter",
      "kind": "encyclopedia",
      "title": "Peashooter",
      "subtitle": "Plant file · Owned by default",
      "path": "/en/encyclopedia/peashooter/",
      "url": "https://pvzvoyage.com/en/encyclopedia/peashooter/",
      "aliases": [
        "peashooter",
        "Peashooter",
        "plant"
      ]
    }
  ]
}

curl

curl -fsSL \
  -A "PVZVoyage/0.17q (+https://pvzvoyage.com/)" \
  https://pvzvoyage.com/api/v1/search/en.json

fetch

// Server-side fetch example. Browser fetch sends a browser User-Agent automatically.
const response = await fetch("https://pvzvoyage.com/api/v1/search/en.json", {
  headers: {
    "User-Agent": "PVZVoyage/0.17q (+https://pvzvoyage.com/)",
  },
});
if (!response.ok) {
  throw new Error(`PVZ: Voyage API failed: ${response.status}`);
}
const searchIndex = await response.json();
  • Root search-index-*.json files are for the site UI and are not the stable API.
GET

News and announcements JSON

/api/v1/news/{locale}.json
Best for
Launcher news panes, Discord/QQ bots, aggregators
Cache
Cache-Control: public, max-age=300, stale-while-revalidate=21600; CDN-Cache-Control: public, max-age=21600, stale-while-revalidate=86400
Example path
/api/v1/news/en.json

Field summary

Field summary
FieldTypeDescription
latestVersion string Latest version.
links object Home, download, changelog, RSS, and release API links.
items array Announcement items.
items[].highlights array Structured highlights.

News feed example

{
  "schemaVersion": 1,
  "kind": "newsFeed",
  "locale": "en",
  "latestVersion": "0.17q",
  "items": [
    {
      "id": "release-0.17q",
      "kind": "release",
      "version": "0.17q",
      "title": "PVZ: Voyage 0.17q released",
      "publishedAt": "2026-07-01",
      "api": {
        "path": "/api/v1/releases/0.17q.json",
        "url": "https://pvzvoyage.com/api/v1/releases/0.17q.json"
      }
    }
  ]
}

curl

curl -fsSL \
  -A "PVZVoyage/0.17q (+https://pvzvoyage.com/)" \
  https://pvzvoyage.com/api/v1/news/en.json

fetch

// Server-side fetch example. Browser fetch sends a browser User-Agent automatically.
const response = await fetch("https://pvzvoyage.com/api/v1/news/en.json", {
  headers: {
    "User-Agent": "PVZVoyage/0.17q (+https://pvzvoyage.com/)",
  },
});
if (!response.ok) {
  throw new Error(`PVZ: Voyage API failed: ${response.status}`);
}
const news = await response.json();
  • Use RSS for feed readers; use this JSON for software clients.
GET · HEAD

Binary downloads

/download/{version}/{platform}
Best for
Launchers, mirrors, download pages
Cache
public, max-age=3600, s-maxage=86400, immutable
Example path
/download/0.17q/windows

Field summary

Field summary
FieldTypeDescription
Content-Type header Build MIME type, such as application/zip or the APK MIME type.
Content-Disposition header Attachment filename.
Content-Length header Byte size of the full file or single-range response.
Accept-Ranges header Supports bytes.
Range request header Request a single byte range.
Retry-After header Returned on 429 responses.

HEAD probe example

HTTP/2 200
content-type: application/vnd.microsoft.portable-executable
content-length: 151519712
content-disposition: attachment; filename="PVZ-Voyage-0.17q-Windows-x64.exe"
accept-ranges: bytes
cache-control: public, max-age=3600, s-maxage=86400, immutable

HEAD

curl -I \
  -A "PVZVoyage/0.17q (+https://pvzvoyage.com/)" \
  https://pvzvoyage.com/download/0.17q/windows

Range

curl -L \
  -A "PVZVoyage/0.17q (+https://pvzvoyage.com/)" \
  -H "Range: bytes=0-1048575" \
  -o pvzvoyage.part \
  https://pvzvoyage.com/download/0.17q/windows

fetch

// Server-side fetch example. Browser fetch sends a browser User-Agent automatically.
const head = await fetch("https://pvzvoyage.com/download/0.17q/windows", {
  method: "HEAD",
  headers: {
    "User-Agent": "PVZVoyage/0.17q (+https://pvzvoyage.com/)",
  },
});
const size = Number(head.headers.get("content-length") ?? 0);
const filename = head.headers.get("content-disposition");
  • Read release detail first, then use builds.*.url.
  • After the download completes, use builds.*.sha256 to verify that the file matches the build published by this site.
  • Only single byte ranges are supported; multi-ranges or invalid ranges return 416. On 429, read Retry-After before retrying.
GET

RSS release feed

/feed.xml
Best for
RSS readers and subscription services
Cache
static asset cache through server response headers
Example path
/feed.xml

Field summary

Field summary
FieldTypeDescription
channel RSS Release channel.
item RSS Release item.
link RSS Localized changelog link.
pubDate RSS Release date.

RSS fragment

<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>PVZ: Voyage</title>
    <item>
      <title>PVZ: Voyage 0.17q released</title>
      <link>https://pvzvoyage.com/en/version/0.17q/</link>
    </item>
  </channel>
</rss>

curl

curl -fsSL \
  -A "PVZVoyage/0.17q (+https://pvzvoyage.com/)" \
  https://pvzvoyage.com/feed.xml

fetch

// Server-side fetch example. Browser fetch sends a browser User-Agent automatically.
const response = await fetch("https://pvzvoyage.com/feed.xml", {
  headers: {
    "User-Agent": "PVZVoyage/0.17q (+https://pvzvoyage.com/)",
  },
});
const xml = await response.text();
  • RSS is for readers; software clients should prefer /api/v1/news/{locale}.json.

Download client guidance

Do not hardcode binary paths in clients. Read latest release detail first, then choose a platform from builds. Download routes support HEAD probes, GET downloads, and single-range resumes; invalid ranges return 416, and rate limiting returns 429 with Retry-After. After completion, use SHA-256 to check whether the local file matches the build published by this site; that check does not prove runtime compatibility, antivirus decisions, or game behavior.

JSON or RSS?

Both are generated by the same static build pipeline, but they serve different consumers.

Use News JSON

Structured fields, CTAs, images, tags, and release API links are better for software.

  • Launcher news panes
  • Bot announcements
  • External aggregators
/api/v1/news/en.json

Use RSS

RSS 2.0 is better for feed readers and generic subscription services.

  • RSS readers
  • Personal subscriptions
  • Low-maintenance notifications
/feed.xml

Headers, errors, and versioning

User-Agent

Command-line, script, official game-client, and server-side clients should send a User-Agent that identifies the integration, for example: PVZVoyage/0.17q (+https://pvzvoyage.com/). If you maintain your own client, you may replace the product/version and support URL. Public read APIs support this shape and remain subject to rate limits and abuse controls; browser fetch uses the browser User-Agent automatically; requests with default tool UAs may receive a plain 404 instead of a JSON error.

CORS

JSON API responses are readable cross-origin with Access-Control-Allow-Origin: *. This means browsers may read the response; it does not mean a third-party page is endorsed by this site.

Caching and request rate

API JSON uses Cache-Control for short browser caching and CDN-Cache-Control for longer shared-cache intent; version-pinned release/changelog JSON is more cacheable than latest/update JSON. DidYouKnow random selections enter the shared cache under a normalized effective query. DIY public GDF/icon GET responses use short TTL caching, while HEAD requests do not fill the edge cache and binary downloads use immutable versioned-path caching. Clients should respect these headers and avoid replacing cache with high-frequency polling. On 429, read Retry-After before retrying.

Error responses

Unknown .json paths usually return JSON 404, and bad parameters may return JSON 400. Download routes return 416 for invalid Range requests; requests sent too frequently return 429. Some requests that do not follow public calling conventions may receive a plain 404, and that response may not use the JSON error shape.

Download verification

builds.*.sha256 is used to confirm that the completed local file matches the build published by this site. It does not prove that the file will run on every system, and it does not replace operating-system permissions, antivirus decisions, or your own source checks.

Compatibility

/api/v1 is the breaking-change compatibility boundary. Within v1, schemaVersion only identifies backward-compatible payload evolution; additive fields may raise it, and clients must ignore unknown fields. Any incompatible change moves to /api/v2.