Ghost Story-5

Debug Ghosts, Part V

January 01, 2026

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:

<?php
declare(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

Comments