Debug Ghosts, Part V
When “Invalid API Key” Wasn’t About the API Key
The Setup
I was deep into staging Focus on Life on A2 Hosting when TinyMCE suddenly refused to load.
The editor reported the familiar but unhelpful message:
Invalid API key.
That was puzzling, because:
-
the same TinyMCE setup worked perfectly on Ubuntu
-
I had just created a fresh TinyMCE account
-
the correct domain (
thefocusonlife.org) was registered
This felt like a configuration issue — but it wasn’t that simple.
The First Clue (and the First Ghost)
DevTools told a quieter, more revealing story.
The TinyMCE script was loading from:
https://cdn.tiny.cloud/1//tinymce/6/tinymce.min.js
That double slash (1//) meant the API key wasn’t invalid —
it was missing entirely.
So the ghost wasn’t TinyMCE.
It was something upstream.
Ghost #1: Two Entry Points, One Missed Config
My staging layout had two index files:
-
_stage/index.php(tiny, innocuous) -
_stage/public/index.php(the real front controller)
Requests to /_stage/... were entering through the small index file —
which did not load the A2 config early enough.
Twig rendered the layout before the TinyMCE API key existed.
No error.
No warning.
Just an empty constant.
Classic Debug Ghost.
Ghost #2: Twig Was Innocent
The Twig code was correct:
<script src="https://cdn.tiny.cloud/1/{{ constant('TINYMCE_API_KEY') }}/tinymce/6/tinymce.min.js"></script>
Twig did exactly what it was told:
-
constant exists → render value
-
constant doesn’t exist → render nothing
Twig wasn’t broken.
Twig was honest.
Ghost #3: The Real Culprit — Misusing $_ENV
This was the line that caused the most damage:
define('TINYMCE_API_KEY', $_ENV['actual_api_key_string'] ?? '');
I had accidentally used the API key itself as the environment variable name.
So PHP was looking for an environment variable literally named something like:
lxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxq
It didn’t exist — so PHP silently returned an empty string.
No fatal error.
No notice.
Just a missing value that looked like a third-party failure.
This was the actual ghost.
The Fix (Once the Ghosts Were Named)
Step 1: Normalize the Entry Point
Make _stage/index.php explicitly hand off to the real app:
<?phpdeclare(strict_types=1);require_once __DIR__ . '/config/config.a2.php';require_once __DIR__ . '/public/index.php';
One entry path. One config load. No ambiguity.
Step 2: Define the Key Deterministically (for staging)
Until .env loading is fully validated on A2:
define('TINYMCE_API_KEY', 'my_actual_key_here');
Predictability beats elegance during migration.
Step 3: Verify What the Browser Sees
After the fix, View Source finally showed:
https://cdn.tiny.cloud/1/MY_REAL_KEY/tinymce/6/tinymce.min.js
TinyMCE loaded instantly.
No warnings.
No drama.
No ghosts.
What This Debug Ghost Taught Me
1. “Invalid API Key” Often Means “Config Didn’t Load”
Third-party errors frequently mask your own load-order problems.
2. Multiple Entry Points Are Silent Trouble
If you have more than one index.php, you will eventually debug the wrong one.
3. Twig Will Never Save You From PHP
Twig renders what PHP gives it — nothing more, nothing less.
4. Environment Variables Demand Precision
$_ENV['TINYMCE_API_KEY'] ✔$_ENV['actual_key_string'] ❌
One character mistake can cost hours.
Why This Was Worth Writing Down
This wasn’t really about TinyMCE.
It exposed:
-
a fragile staging bootstrap
-
a hidden entry-point divergence
-
an env-handling assumption that would have failed again later
Catching it in staging saved a future production outage.
That’s the value of Debug Ghosts.
Coming Up Next
-
Cleaning up the remaining staging warnings
-
Deciding whether TinyMCE image uploads belong inside the editor
-
Preparing the production cutover with fewer ghosts waiting in the dark
For now, the editor works — and more importantly, I understand why.
Posted in ghost-stories by TFOL BLOG