After writing the first wave of Lubinik case studies and journal posts, another problem became obvious.
Lubinik was no longer only a WordPress theme and plugin system. It had become a place where real structured data lived: case studies, taxonomies, galleries, menus, settings, FAQ entries, blog posts, shortcode-managed sections, and addon-specific content models.
And once data lives somewhere properly, the next question is always the same: what happens if another interface needs it?
I do not want a second source of truth
The example in my head was a future app for a Lubinik site like Musi Sereni. If the animal profiles, taxonomies, galleries, adoption status, and public information already exist in WordPress, rebuilding all of that in a separate app database would be absurd.
It would create another admin, another import process, another place where things can drift out of sync, and another thing to maintain. That is exactly the kind of repetition Lubinik was built to remove.
So the idea for Lubinik API became very simple: WordPress remains the source of truth, but another frontend can ask for the public data in a clean format.
Public data does not mean careless access
The first important boundary was that this plugin should be read-only. It is not a form handler. It is not an admin API. It does not create posts, edit posts, delete posts, expose drafts, or leak private meta because an app might find it convenient.
The second boundary was more subtle: public-facing data does not mean unlimited anonymous traffic forever. A read-only API can still be abused by bots, heavy scraping, or accidental overuse. So the plugin needed a proper access layer from the beginning: anonymous reads when wanted, tokens when needed, CORS config, rate limits, and health checks that explain the current mode.
That felt very Lubinik: not paranoid, not naive. Just explicit.
The same config philosophy again
I did not want the API plugin to know every future addon by name. That would defeat the whole point of the addon architecture.
So Lubinik API follows the same rule as the SEO and structured data plugins: the engine is generic, and the project knowledge lives near the layer that owns it.
- the core plugin can describe shared plugin-side settings and providers;
- the parent theme can expose generic site settings, menus, and theme shortcodes;
- an addon can describe its CPTs, taxonomies, collections, and data providers;
- a child theme can decide what this specific site should expose or lock down.
That is why the config lives in config/api, just like the other support plugins have their own config folders. It means the API can be extended without teaching the API plugin every detail of every site.
Shortcodes as data, not HTML
One of the interesting parts was the shortcode manager. Lubinik uses shortcodes a lot, but an API should not blindly return rendered shortcode HTML. That HTML belongs to the WordPress theme, not to a mobile app or a headless frontend.
So the API exposes shortcode data providers instead. A gallery shortcode can return media objects. A list shortcode can return collection items. A FAQ shortcode can return questions and answers. Unknown shortcodes do not need to pretend to be API-ready.
That keeps the separation clean: WordPress can render pages, but external clients can consume data.
A doorway, not a rewrite
The first working version already exposes the pieces I need for the Lubinik site itself: manifest, schema, health, entities, collections, taxonomies, terms, media, menus, settings, search, pages, entity pages, and shortcode data endpoints.
That sounds like a lot, but it is not a separate platform. It is just a clean doorway into the structured content that already exists.
And that is the point. Lubinik API is not there to make the system flashier. It is there so that if the same data needs to travel to another interface one day, I do not have to rebuild the same truth somewhere else.
Apparently the next stage of not rebuilding things is not rebuilding the database either.