Skip to main content

REST URL Unification Plan (Phase 2)

Status: phase 2 is done. PHP, OpenAPI, skills, and clients use canonical {product}/{slug}/v1. There is no dual-register / alias / rewrite. Old URLs return 404. The tables below are the historical old → new mapping.

This page was a work plan. PHP namespaces were not changed in phase 1; phase 2 switched URLs without dual routes.

Input: endpoint inventory (current URLs).

Unified Scheme

All REST APIs of all products use one form:

{product}/{slug}/v1
PartValue
{product}Product REST prefix. Plugins: wp2lms, wp2lms-pro, wp2tutor, wp2tutor-forms, wp2tutor-bot, wp2app. WP2 Platform is an exception: the URL uses wp2, not wp2platform. The plugin id / repository remain wp2platform.
{slug}module identifier (whether in the plugin core or an add-on makes no difference)
v1REST version of this family

There is no separate “core = {product}/v1” pattern. A new module registers only {product}/{slug}/v1. {product} is the owner's REST prefix (for platform, this is wp2, not the plugin id).

Forbidden in new code:

  • {product}/module/{slug}/v1
  • {product}/v1 without a module segment
  • REST prefix wp2platform/… (only in old code being removed)
  • a foreign root (wp2lms-local-sync, wp2turbo, shared wp2tutor/v1 for forms/bot)

Old addresses are not supported: no dual registration, no deprecated alias in OpenAPI, no nginx rewrite, no “sunset period.” In a single change, PHP registers only the canonical form, while clients (Studio, skills, SPA, CLI, admin UIs) migrate at the same time. The old URL returns 404.

Reference implementations of the {product}/{slug}/v1 form already in the code:

  • Embed Packages wp2lms-pro/embed-packages/v1
  • Studio Sync wp2/studio-sync/v1 — PHP src/Modules/StudioSync/, client STUDIO_SYNC_REST_BASE, CLI wp studio-sync. Do not rework (including changing it to wp2/studio/v1).

Media Playback is currently wp2/media-playback/v1 — in phase 2 it remains wp2/media-playback/v1. The Embed Packages dual registration (wp2platform/module/embed-packages/v1) is removed.

Path Composition: Slug Only in Namespace

Full URL: /wp-json/{product}/{slug}/v1{route}.

Do not repeat {slug} in {route}. If the slug is currently in the route (wp2/license/v1/products), after migration it moves into the namespace, and the route begins with the next segment (/products).

BeforeAfter
{product}/module/{slug}/v1{route}{product}/{slug}/v1{route} — the same {route}
{product}/v1/{slug}{route}{product}/{slug}/v1{route}
foreign root {legacy}/v1{route}{product}/{slug}/v1{route}

The only exception is when {route} would become empty after removing the slug: the canonical form is then GET|POST …/{product}/{slug}/v1 (module root) or an explicit short resource (/chat). One option is selected below for each such family, without alternatives.

blog_id and other query parameters are not part of the namespace; they remain query parameters as they are now.

Namespace Old → New

Rule for the remaining A platform families: remove /module/ and replace the wp2platform prefix with wp2. For B platform families: wp2/{slug}/v1. For D: wp2/{slug}/v1. Other plugins: {plugin-id}/{slug}/v1 as shown in the table.

Studio Sync is already canonical (wp2/studio-sync/v1) — its table row is for reference, not a phase 2 task.

FamilyCurrentTarget
Site Managerwp2/site-manager/v1wp2/site-manager/v1
Site Duplicatorwp2/site-duplicator/v1wp2/site-duplicator/v1
Post Duplicatorwp2/post-duplicator/v1wp2/post-duplicator/v1
Multi Networkwp2/multi-network/v1wp2/multi-network/v1
Media Converterwp2/media-converter/v1wp2/media-converter/v1
Subtitles & Timecodeswp2/media-subtitles-timecodes/v1wp2/media-subtitles-timecodes/v1
Enable Media Replacewp2/enable-media-replace/v1wp2/enable-media-replace/v1
Studio Syncwp2/studio-sync/v1already canonical — do not touch
Passwordlesswp2/passwordless/v1wp2/passwordless/v1
Magic Linkswp2/magic-links/v1wp2/magic-links/v1
Media Playbackwp2/media-playback/v1wp2/media-playback/v1
Licensewp2/license/v1/…wp2/license/v1
Sections Folderwp2/sections-folder/v1/…wp2/sections-folder/v1
Elementor Style Presetswp2/elementor-style-presets/v1/…wp2/elementor-style-presets/v1
Local Syncwp2/local-sync/v1wp2/local-sync/v1
Static Builderwp2lms/static-builder/v1wp2lms/static-builder/v1
Turbowp2lms/turbo/v1wp2lms/turbo/v1
Embed Packages canonicalwp2lms-pro/embed-packages/v1no change
Embed Packages aliaswp2platform/module/embed-packages/v1remove registration; canonical form only: wp2lms-pro/embed-packages/v1
Tutor AIwp2tutor/v1 + /ai-chatwp2tutor/ai/v1
Tutor Embeddingwp2tutor/v1 + /embedding/…wp2tutor/embedding/v1
Tutor RAGwp2tutor/v1 + /rag-chunks/…wp2tutor/rag/v1
Formswp2tutor/v1 + /wp2tutor-form/form/…wp2tutor-forms/form/v1
Forms i18nwp2tutor/v1 + /wp2tutor-form/i18n/…wp2tutor-forms/i18n/v1
Bot Telegram / VK / Deep Chatwp2tutor/v1 + /telegram|vk|deepchat/…wp2tutor-bot/telegram/v1, …/vk/v1, …/deepchat/v1
wp2app Auth / Content / AIwp2app/v1wp2app/auth/v1, wp2app/content/v1, wp2app/ai-chat/v1

Full URLs (Where the Route Is Not “the Same Suffix”)

Platform A families: {route} is the same as in the inventory, NS wp2/{slug}/v1. The B, D, tutor, forms, bot, and app families are listed below. Studio Sync is not included in the URL table because it is already wp2/studio-sync/v1.

The prefix is /wp-json/ everywhere.

Studio Sync — Complete, Outside Phase 2 Work

Code: onepix/wp2platform/src/Modules/StudioSync/ (AbstractRestController → namespace wp2/studio-sync/v1). OpenAPI: the same paths in wp2platform-modules.yaml. Client: onepix/wp2studio/electron/runtime/core/constants.ts (STUDIO_SYNC_REST_BASE). CLI: wp studio-sync.

Do not: rename again, use wp2/studio/v1, or dual-register with the old wp2platform/module/wp2sync/v1.

License

METHODCurrentTarget
GETwp2/license/v1/productswp2/license/v1/products
POSTwp2/license/v1/{product_id}/activatewp2/license/v1/{product_id}/activate
POST…/deactivatewp2/license/v1/{product_id}/deactivate
GET…/statuswp2/license/v1/{product_id}/status
GET…/datawp2/license/v1/{product_id}/data

Sections Folder

METHODCurrentTarget
GETwp2/sections-folder/v1/sidebarwp2/sections-folder/v1/sidebar
PUT…/reorderwp2/sections-folder/v1/reorder
PUT…/assignwp2/sections-folder/v1/assign
PATCH…/sections/{id}wp2/sections-folder/v1/sections/{id}

Elementor Style Presets

METHODCurrentTarget
GET / POSTwp2/elementor-style-presets/v1/presetswp2/elementor-style-presets/v1/presets
GET / PATCH…/presets/{id}wp2/elementor-style-presets/v1/presets/{id}

Local Sync

METHODCurrentTarget
GETwp2/local-sync/v1/projectswp2/local-sync/v1/projects
POST…/syncwp2/local-sync/v1/sync
POST…/validatewp2/local-sync/v1/validate

CLI (pages-cli / local-sync), if it calls REST or prints NS, uses the same canonical form.

Static Builder (Same Suffix)

wp2lms/static-builder/v1/{pages,builds,…}wp2lms/static-builder/v1/{same route}. Migrate CLI bin/static-builder/* together with skill 10.

Turbo

METHODCurrentTarget
POSTwp2lms/turbo/v1/xapi-actor/sessionwp2lms/turbo/v1/xapi-actor/session
POSTwp2lms/turbo/v1/gravity-forms/submitwp2lms/turbo/v1/gravity-forms/submit
POSTwp2lms/turbo/v1/dsh/expandwp2lms/turbo/v1/dsh/expand
GETwp2lms/turbo/v1/static/{id}wp2lms/turbo/v1/static/{id}

Tutor

METHODCurrentTarget
GET / POSTwp2tutor/v1/ai-chatwp2tutor/ai/v1/chat
POSTwp2tutor/v1/embedding/syncwp2tutor/embedding/v1/sync
POST…/embedding/statuseswp2tutor/embedding/v1/statuses
POST…/embedding/diagnosticswp2tutor/embedding/v1/diagnostics
GET / POSTwp2tutor/v1/rag-chunks/datatableswp2tutor/rag/v1/datatables
GET…/rag-chunks/chunk/{id_chunk}wp2tutor/rag/v1/chunk/{id_chunk}

Forms

METHODCurrentTarget
POSTwp2tutor/v1/wp2tutor-form/form/updateDefinitionwp2tutor-forms/form/v1/updateDefinition
POST…/updateShortcodewp2tutor-forms/form/v1/updateShortcode
POST…/updateStyleswp2tutor-forms/form/v1/updateStyles
POST…/updateL10nwp2tutor-forms/form/v1/updateL10n
POST…/updateRunnerwp2tutor-forms/form/v1/updateRunner
GET…/getRunnerDatawp2tutor-forms/form/v1/getRunnerData
GETwp2tutor/v1/wp2tutor-form/i18n/localewp2tutor-forms/i18n/v1/locale
GET…/i18n/translationwp2tutor-forms/i18n/v1/translation

Bot

METHODCurrentTarget
POSTwp2tutor/v1/telegram/webhook/{bot_id}wp2tutor-bot/telegram/v1/webhook/{bot_id}
POSTwp2tutor/v1/vk/callback/{channel_id}wp2tutor-bot/vk/v1/callback/{channel_id}
POST / OPTIONSwp2tutor/v1/deepchat/chat/{channel_id}wp2tutor-bot/deepchat/v1/chat/{channel_id}

wp2app

METHODCurrentTarget
GETwp2app/v1/mewp2app/auth/v1/me
POSTwp2app/v1/loginwp2app/auth/v1/login
POSTwp2app/v1/logoutwp2app/auth/v1/logout
GETwp2app/v1/noncewp2app/auth/v1/nonce
POSTwp2app/v1/lost-passwordwp2app/auth/v1/lost-password
POSTwp2app/v1/reset-passwordwp2app/auth/v1/reset-password
GETwp2app/v1/contentwp2app/content/v1 (query path as now)
GETwp2app/v1/content/{id}wp2app/content/v1/{id}
POST / OPTIONSwp2app/v1/ai-chat/chatwp2app/ai-chat/v1/chat

Studio Sync — Out of Scope

The WP2 Sync → Studio Sync rename and canonical wp2/studio-sync/v1 form are already complete. This plan does not repeat them or change the slug to studio.

The Playground bundle in wp2studio is a platform mirror and must be updated through the build.

RestController (wordpress-core)

Current: __construct($app_name) → namespace {app_name}/v1, route_path = namespace + rest_base. This is precisely the forbidden B pattern.

Target:

namespace = {product}/{slug}/v{n}
rest_base = resource within the module (not a duplicate slug)

Constructor: product + slug (both non-empty). License: ('wp2', 'license') — REST prefix wp2, while the plugin remains wp2platform. rest_base does not duplicate the slug.

One namespace per controller — the canonical one. Do not register the old {app_name}/v1.

Do not retain the one-argument constructor: subclasses (platform, tutor, forms) migrate in the same change as core.

Modules that do not use this base (Studio Sync, already at wp2/studio-sync/v1; Local Sync; Static Builder; Turbo; wp2app; bot) set the {product}/{slug}/v1 string according to the table.

Compatibility

None. The old REST URL, CLI name, and OpenAPI path are removed in the same change that introduces the canonical form. Do not rename WP_Error codes in this epic (they are not URLs).

The client and server for a family are deployed together. Do not deploy a plugin with the new NS until the skill / SPA / admin UI for that family has been migrated. Bot webhooks: change the URL in Telegram/VK administration when deploying.

Clients (Grep for Each Family)

FamilyWhere to Update
Studio Syncdo not touch — already wp2/studio-sync/v1
Static Builder.agents/skills/10-static-builder; onepix/wp2lms/bin/static-builder/*
Media Converter / subtitles.agents/skills/media-hls-encode, media-whisper-tracks (_common.py NS)
Passwordless / Magic Linksonepix/wp2app/assets/src/auth/AuthContext.tsx; PrivateSiteExemption.php; RestLockdown.php
Local Sync.agents/skills/09-local-sync, 06-page-builder; PHP Local Sync REST already
Turboonepix/wp2lms/src/Modules/Turbo/**; Pulse/MOS bundles if they hardcode wp2lms/turbo/v1
Formsbuilder JS / rest_url in wp2tutor-forms; wp2tutor/rest_controllers filter
Bot + Deep Chatwebhook URL registration in channel admin UIs; wp2app ChatProxy wp2tutor/v1/deepchat
wp2appRestAuthController, ClassicContentController, ChatProxyController, UrlHelper boot restUrl
License / Sections / Presetsadmin JS/PHP get_rest_url() / rest_url() in those modules
Embed Packages aliasagents/docs that still call wp2platform/module/embed-packages
Site Manager and other A familiesadmin UIs of the respective modules (rest_url / DualRest)

Still out of scope: AnalogWP, /wp/v2/*, AJAX. In phase 2 scope: WP-CLI for the same families — new names immediately, without aliases for old commands.

Public bot webhooks: during deployment, the operator must change the URL in Telegram/VK. The old path does not respond.

audit:openapi Audit

  • Only the canonical {product}/{slug}/v1 form in PHP and YAML.
  • Fail if a PHP endpoint is absent from YAML, or if YAML/PHP still contains /module/, bare {product}/v1, REST prefix wp2platform/, wp2lms-local-sync, wp2turbo, the embed-packages alias, or shared wp2tutor/v1 for forms/bot.
  • Do not treat NAMESPACE_ALIAS in the scanner as normal: there is no second NS.
  • Invoke from wp2-site prebuild as now.
  • Do not parse OpenAPI from PHP as the sole canonical source.

Tests (Family DoD)

  • PHPUnit / REST: the canonical URL responds.
  • The family's old URL returns 404 (negative test).
  • Forms/bot: no registration on wp2tutor/v1.

Work Order

The client and PHP for one family comprise one release (linked MRs are merged together).

  1. Infrastructure: RestController (product, slug); audit only the canonical form.
  2. Remove the Embed Packages alias; do not touch canonical wp2lms-pro/embed-packages/v1.
  3. Remaining A platform families + Static Builder: platform → wp2/{slug}/v1 (not wp2platform/…); Static Builder → wp2lms/static-builder/v1. Update clients immediately (skills 10 / HLS / Whisper, passwordless, admin UIs). Skip Studio Sync.
  4. B platform: License, Sections Folder, Presets.
  5. Local Sync + skill 09 + CLI.
  6. Turbo + Pulse/MOS bundles.
  7. Tutor: ai / embedding / rag.
  8. Forms: its own product, decouple wp2tutor/rest_controllers.
  9. wp2app: auth / content / ai-chat.
  10. Bot: new webhook URLs; change them in channel administration in the same deployment.

First code: steps 2–3. Do not add Studio Sync to the queue.

Family Completion Criteria

  • Canonical form strictly {product}/{slug}/v1, route without a duplicate slug, URLs as shown in the tables above.
  • Only the canonical form in PHP and OpenAPI: platform uses wp2/{slug}/v1, other plugins use {plugin-id}/{slug}/v1. No /module/, no bare {product}/v1, no REST prefix wp2platform/.
  • npm run audit:openapi passes.
  • Clients from the grep table and CLI are migrated in the same change.
  • DoD tests pass, including 404 for the old URL.
  • No references to the old path in onepix/ (except negative tests). Update the Playground mirror through the build, not manually.

Phase 2 is complete (steps 1–10). Studio Sync remains out of scope (already canonical).