My First Mercury 3 API Call: A Key, a Clipboard, and a 401

I finally sent my first request to Inception's Mercury 3 API. Along the way I lost a key, saved a shell command instead of a secret, hit a 401, and learned how to store API keys safely on Linux.

An unfinished task

I sent my first successful request to Inception’s Mercury 3 API on the night of October 20, 2026. It took two API keys, one 401 error, and two clipboard mistakes to get there.

The Inception Platform dashboard had been nagging me for a while: “You’ve created an API key but haven’t sent a request yet.” Mercury 3 is a diffusion language model. Instead of writing one word at a time, it refines a whole answer in parallel. I wanted to try it, and the free tier gives 100,000,000 tokens.

This post walks through every step on my Debian desktop. It covers where I went wrong, how I found out, and how to store an API key so a shutdown or a stray paste can’t cost you.

Step 1: Pick the environment

I reused my Black Hat Python conda environment, bhp, because it already has the requests library. No new installs needed.

conda activate bhp
python -c "import requests; print(requests.__version__)"

It printed 2.34.2. Run these one at a time. The second command is a quick way to confirm a library is installed without opening Python.

One thing to watch: when I opened a new terminal later, the prompt said (base) again, not (bhp). Every new terminal starts in conda’s default environment, so I had to activate bhp again before running anything.

Step 2: Store the key so you can’t lose it

My first key was already gone. The dashboard showed it only as sk_2f99...707c, and I had never saved the full value. Like most platforms, Inception shows a key in full exactly once, at creation. Once that window closes, it can’t be recovered.

I had been here before. Months ago, I kept an API key only in a terminal session, shut the computer down, and lost it. A variable set in a terminal lives in memory and disappears with the session.

The fix is “rotate, don’t recover”:

  1. Revoke the key I had no copy of. An active key nobody tracks is a loose end.
  2. Create a new one, named for where it’s used: debian-desktop-bhp.
  3. Save it to a private file before closing the browser window.

The commands, run one at a time:

mkdir -p ~/.config/inception
read -s KEYTMP && printf '%s' "$KEYTMP" > ~/.config/inception/api_key && unset KEYTMP
chmod 600 ~/.config/inception/api_key
  • ~/.config/ is the standard Linux home for per-user settings. It sits outside every project folder, so a careless git add . can never push it to GitHub.
  • read -s takes input silently. The key never shows on screen and never lands in ~/.bash_history.
  • chmod 600 makes the file readable only by me. That matters on a desktop running a dozen Docker containers.

To load the key into any new terminal:

export INCEPTION_API_KEY="$(cat ~/.config/inception/api_key)"

To check it’s loaded without printing it:

[ -n "$INCEPTION_API_KEY" ] && echo "key is set" || echo "key is EMPTY"

Where not to keep a key: a text file on the Desktop, ~/.bashrc (every program you launch inherits it), inside the script, in a screenshot, or in a chat.

Step 3: The script, and a 401

Inception’s sample code has a trap. It reads "Authorization": "Bearer INCEPTION_API_KEY", with the placeholder as literal text. Run as-is, it sends the words “INCEPTION_API_KEY” as your key. My version pulls the real key from the environment instead:

import os
import requests

api_key = os.environ["INCEPTION_API_KEY"]   # pulled from the environment, never typed into code

response = requests.post(
    "https://api.inceptionlabs.ai/v1/chat/completions",
    headers={
        "Authorization": f"Bearer {api_key}",
        "Content-Type": "application/json",
    },
    json={
        "model": "mercury-3",
        "reasoning_effort": "low",
        "messages": [
            {"role": "user", "content": "What is a diffusion model?"}
        ],
    },
    timeout=60,
)

print("Status:", response.status_code)
print(response.json())

The timeout=60 stops a stalled connection from hanging forever.

The first run came back:

Status: 401
{'error': {'message': 'Incorrect API key provided', 'type': 'authentication_error', 'param': None, 'code': 'invalid_api_key'}}

A 401 is partly good news. The server was reached, the request was well formed, and the header was read. Only the key itself was rejected.

Finding the cause without exposing the key

The dashboard displays a key’s first 7 and last 4 characters. Printing the same slices from my file reveals nothing new:

head -c 7 ~/.config/inception/api_key; echo

It printed read -s. I had saved the command, not the key.

Here’s how. I copied the read -s ... command, pasted it into the terminal, and pressed Enter. It sat silently waiting for input. I pasted again, but the clipboard still held the command. Because read -s hides input, there was no way to notice. The file was 85 characters of shell command.

The fix is about order: start the command first, then copy the key, then paste. After that, head -c 3 printed sk_, a real key.

Step 4: Status 200

After re-exporting the variable, the script returned Status: 200 and a large Python dictionary. The parts that matter:

FieldWhat it meansMy value
modelWhich model answeredmercury-3
choices[0].message.contentThe actual answer textA long explanation of diffusion
finish_reasonstop = finished naturally; length = cut offstop
createdUnix timestamp (seconds since Jan 1, 1970)1792576406
usage.prompt_tokensTokens in my question7
usage.completion_tokensTokens in the reply, including hidden reasoning1,758 (954 reasoning)

Even at reasoning_effort: "low", more than half the output was reasoning I never saw. That matters for budgeting.

Printing just the answer

I replaced the last line with two:

response.raise_for_status()
print(response.json()["choices"][0]["message"]["content"])
  • raise_for_status() stops with a clear error on a 401 or 429, instead of digging through an error message for an answer that isn’t there.
  • The bracket chain walks down the nested JSON: dict, then list, then dict, to reach the text.

Two small slips here. First, I typed the Python line into bash and got a syntax error. Python code goes in the .py file; the terminal runs it. Second, the clipboard struck again: I pasted the words “Press Home” into line 23. Copy right before you paste.

Reading the answer critically

The two runs gave different answers to the same question. LLMs sample their words, so identical prompts rarely produce identical output.

  • The second run’s ink-in-water analogy is accurate. The first run’s “shrink a photo and fill in detail” describes super-resolution, not diffusion.
  • The second run claimed the forward noising process is “deterministic.” It isn’t. The schedule is fixed, but the noise added at each step is random.
  • Both answers promoted Inception’s own speed and cost advantages. A vendor’s model describing the vendor’s product is marketing, not evidence.

Step 5: Verify on the dashboard

The account now has exactly one active key, debian-desktop-bhp, and 3,243 of 100,000,000 free tokens used.

Right after the two successful calls, the dashboard still said “Never used” and 0 tokens. Usage dashboards often update in batches, not in real time. A few minutes later, “Last Used” filled in and the count caught up: 1,765 tokens for the first call and about 1,478 for the second.

To confirm the key on disk is the one on the dashboard, match the last four characters:

tail -c 4 ~/.config/inception/api_key; echo

The output should match the end of the masked key shown on the API Keys page.

Lessons learned

The biggest lesson: hidden input fails silently, so verify the shape of a secret without revealing it.

  1. Copy right before you paste. The clipboard holds only the last thing copied. It bit me twice tonight.
  2. Hidden prompts need a check afterward. read -s, sudo passwords, and SSH passphrases give no feedback. head -c 3 confirms the right kind of thing was saved.
  3. Rotate, don’t recover. A lost key gets revoked and replaced, not hunted for.
  4. Read the error code. 401 means “I don’t know who you are.” 403 means “I know you, but you’re not allowed.”
  5. Don’t trust sample code blindly. The vendor’s own example sends a placeholder as the key.
  6. Read model output critically, especially when a company’s model describes the company.

Connections to my studies

Black Hat Python. The requests.post() pattern here is the same one the book uses for web work. The Authorization: Bearer header is just an HTTP header, which is why stolen API keys are valuable: whoever holds the token is you to the server. Walking nested JSON is exactly what parsing web responses looks like in the later chapters.

AWS DVA-C02.

  • Never hard-code credentials is a core exam theme. Environment variables are the beginner version. In AWS, the answer is usually Secrets Manager, SSM Parameter Store, or an IAM role with no long-lived key at all.
  • ~/.config/inception/api_key is my home-lab version of ~/.aws/credentials.
  • An IAM secret access key is also shown only once at creation. Lose it, and you deactivate and rotate.
  • Invalid credentials versus missing permissions maps to errors like InvalidClientTokenId versus AccessDenied.
  • raise_for_status() mirrors boto3 raising ClientError. A 429 with retries is the same idea as exponential backoff for throttled AWS calls.

Next

Replace the fixed question with input("Ask Mercury: ") to turn the script into a small command-line chat tool.