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
| Part | Value |
|---|---|
{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) |
v1 | REST 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}/v1without a module segment- REST prefix
wp2platform/…(only in old code being removed) - a foreign root (
wp2lms-local-sync,wp2turbo, sharedwp2tutor/v1for 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— PHPsrc/Modules/StudioSync/, clientSTUDIO_SYNC_REST_BASE, CLIwp studio-sync. Do not rework (including changing it towp2/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).
| Before | After |
|---|---|
{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.
| Family | Current | Target |
|---|---|---|
| Site Manager | wp2/site-manager/v1 | wp2/site-manager/v1 |
| Site Duplicator | wp2/site-duplicator/v1 | wp2/site-duplicator/v1 |
| Post Duplicator | wp2/post-duplicator/v1 | wp2/post-duplicator/v1 |
| Multi Network | wp2/multi-network/v1 | wp2/multi-network/v1 |
| Media Converter | wp2/media-converter/v1 | wp2/media-converter/v1 |
| Subtitles & Timecodes | wp2/media-subtitles-timecodes/v1 | wp2/media-subtitles-timecodes/v1 |
| Enable Media Replace | wp2/enable-media-replace/v1 | wp2/enable-media-replace/v1 |
| Studio Sync | wp2/studio-sync/v1 | already canonical — do not touch |
| Passwordless | wp2/passwordless/v1 | wp2/passwordless/v1 |
| Magic Links | wp2/magic-links/v1 | wp2/magic-links/v1 |
| Media Playback | wp2/media-playback/v1 | wp2/media-playback/v1 |
| License | wp2/license/v1/… | wp2/license/v1 |
| Sections Folder | wp2/sections-folder/v1/… | wp2/sections-folder/v1 |
| Elementor Style Presets | wp2/elementor-style-presets/v1/… | wp2/elementor-style-presets/v1 |
| Local Sync | wp2/local-sync/v1 | wp2/local-sync/v1 |
| Static Builder | wp2lms/static-builder/v1 | wp2lms/static-builder/v1 |
| Turbo | wp2lms/turbo/v1 | wp2lms/turbo/v1 |
| Embed Packages canonical | wp2lms-pro/embed-packages/v1 | no change |
| Embed Packages alias | wp2platform/module/embed-packages/v1 | remove registration; canonical form only: wp2lms-pro/embed-packages/v1 |
| Tutor AI | wp2tutor/v1 + /ai-chat | wp2tutor/ai/v1 |
| Tutor Embedding | wp2tutor/v1 + /embedding/… | wp2tutor/embedding/v1 |
| Tutor RAG | wp2tutor/v1 + /rag-chunks/… | wp2tutor/rag/v1 |
| Forms | wp2tutor/v1 + /wp2tutor-form/form/… | wp2tutor-forms/form/v1 |
| Forms i18n | wp2tutor/v1 + /wp2tutor-form/i18n/… | wp2tutor-forms/i18n/v1 |
| Bot Telegram / VK / Deep Chat | wp2tutor/v1 + /telegram|vk|deepchat/… | wp2tutor-bot/telegram/v1, …/vk/v1, …/deepchat/v1 |
| wp2app Auth / Content / AI | wp2app/v1 | wp2app/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
| METHOD | Current | Target |
|---|---|---|
| GET | wp2/license/v1/products | wp2/license/v1/products |
| POST | wp2/license/v1/{product_id}/activate | wp2/license/v1/{product_id}/activate |
| POST | …/deactivate | wp2/license/v1/{product_id}/deactivate |
| GET | …/status | wp2/license/v1/{product_id}/status |
| GET | …/data | wp2/license/v1/{product_id}/data |
Sections Folder
| METHOD | Current | Target |
|---|---|---|
| GET | wp2/sections-folder/v1/sidebar | wp2/sections-folder/v1/sidebar |
| PUT | …/reorder | wp2/sections-folder/v1/reorder |
| PUT | …/assign | wp2/sections-folder/v1/assign |
| PATCH | …/sections/{id} | wp2/sections-folder/v1/sections/{id} |
Elementor Style Presets
| METHOD | Current | Target |
|---|---|---|
| GET / POST | wp2/elementor-style-presets/v1/presets | wp2/elementor-style-presets/v1/presets |
| GET / PATCH | …/presets/{id} | wp2/elementor-style-presets/v1/presets/{id} |
Local Sync
| METHOD | Current | Target |
|---|---|---|
| GET | wp2/local-sync/v1/projects | wp2/local-sync/v1/projects |
| POST | …/sync | wp2/local-sync/v1/sync |
| POST | …/validate | wp2/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
| METHOD | Current | Target |
|---|---|---|
| POST | wp2lms/turbo/v1/xapi-actor/session | wp2lms/turbo/v1/xapi-actor/session |
| POST | wp2lms/turbo/v1/gravity-forms/submit | wp2lms/turbo/v1/gravity-forms/submit |
| POST | wp2lms/turbo/v1/dsh/expand | wp2lms/turbo/v1/dsh/expand |
| GET | wp2lms/turbo/v1/static/{id} | wp2lms/turbo/v1/static/{id} |
Tutor
| METHOD | Current | Target |
|---|---|---|
| GET / POST | wp2tutor/v1/ai-chat | wp2tutor/ai/v1/chat |
| POST | wp2tutor/v1/embedding/sync | wp2tutor/embedding/v1/sync |
| POST | …/embedding/statuses | wp2tutor/embedding/v1/statuses |
| POST | …/embedding/diagnostics | wp2tutor/embedding/v1/diagnostics |
| GET / POST | wp2tutor/v1/rag-chunks/datatables | wp2tutor/rag/v1/datatables |
| GET | …/rag-chunks/chunk/{id_chunk} | wp2tutor/rag/v1/chunk/{id_chunk} |
Forms
| METHOD | Current | Target |
|---|---|---|
| POST | wp2tutor/v1/wp2tutor-form/form/updateDefinition | wp2tutor-forms/form/v1/updateDefinition |
| POST | …/updateShortcode | wp2tutor-forms/form/v1/updateShortcode |
| POST | …/updateStyles | wp2tutor-forms/form/v1/updateStyles |
| POST | …/updateL10n | wp2tutor-forms/form/v1/updateL10n |
| POST | …/updateRunner | wp2tutor-forms/form/v1/updateRunner |
| GET | …/getRunnerData | wp2tutor-forms/form/v1/getRunnerData |
| GET | wp2tutor/v1/wp2tutor-form/i18n/locale | wp2tutor-forms/i18n/v1/locale |
| GET | …/i18n/translation | wp2tutor-forms/i18n/v1/translation |
Bot
| METHOD | Current | Target |
|---|---|---|
| POST | wp2tutor/v1/telegram/webhook/{bot_id} | wp2tutor-bot/telegram/v1/webhook/{bot_id} |
| POST | wp2tutor/v1/vk/callback/{channel_id} | wp2tutor-bot/vk/v1/callback/{channel_id} |
| POST / OPTIONS | wp2tutor/v1/deepchat/chat/{channel_id} | wp2tutor-bot/deepchat/v1/chat/{channel_id} |
wp2app
| METHOD | Current | Target |
|---|---|---|
| GET | wp2app/v1/me | wp2app/auth/v1/me |
| POST | wp2app/v1/login | wp2app/auth/v1/login |
| POST | wp2app/v1/logout | wp2app/auth/v1/logout |
| GET | wp2app/v1/nonce | wp2app/auth/v1/nonce |
| POST | wp2app/v1/lost-password | wp2app/auth/v1/lost-password |
| POST | wp2app/v1/reset-password | wp2app/auth/v1/reset-password |
| GET | wp2app/v1/content | wp2app/content/v1 (query path as now) |
| GET | wp2app/v1/content/{id} | wp2app/content/v1/{id} |
| POST / OPTIONS | wp2app/v1/ai-chat/chat | wp2app/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)
| Family | Where to Update |
|---|---|
| Studio Sync | do 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 Links | onepix/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 |
| Turbo | onepix/wp2lms/src/Modules/Turbo/**; Pulse/MOS bundles if they hardcode wp2lms/turbo/v1 |
| Forms | builder JS / rest_url in wp2tutor-forms; wp2tutor/rest_controllers filter |
| Bot + Deep Chat | webhook URL registration in channel admin UIs; wp2app ChatProxy wp2tutor/v1/deepchat |
| wp2app | RestAuthController, ClassicContentController, ChatProxyController, UrlHelper boot restUrl |
| License / Sections / Presets | admin JS/PHP get_rest_url() / rest_url() in those modules |
| Embed Packages alias | agents/docs that still call wp2platform/module/embed-packages |
| Site Manager and other A families | admin 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}/v1form in PHP and YAML. - Fail if a PHP endpoint is absent from YAML, or if YAML/PHP still contains
/module/, bare{product}/v1, REST prefixwp2platform/,wp2lms-local-sync,wp2turbo, the embed-packages alias, or sharedwp2tutor/v1for forms/bot. - Do not treat
NAMESPACE_ALIASin the scanner as normal: there is no second NS. - Invoke from wp2-site
prebuildas 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).
- Infrastructure: RestController
(product, slug); audit only the canonical form. - Remove the Embed Packages alias; do not touch canonical
wp2lms-pro/embed-packages/v1. - Remaining A platform families + Static Builder: platform →
wp2/{slug}/v1(notwp2platform/…); Static Builder →wp2lms/static-builder/v1. Update clients immediately (skills 10 / HLS / Whisper, passwordless, admin UIs). Skip Studio Sync. - B platform: License, Sections Folder, Presets.
- Local Sync + skill 09 + CLI.
- Turbo + Pulse/MOS bundles.
- Tutor:
ai/embedding/rag. - Forms: its own product, decouple
wp2tutor/rest_controllers. - wp2app:
auth/content/ai-chat. - 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 prefixwp2platform/. npm run audit:openapipasses.- 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).