Bloggo Docs
What Bloggo is and how it works.

Using your Bloggo key safely with agents

An agent that can publish for you needs a key. The key is a password, and the agent is a program that reads untrusted text all day: web pages, emails, other people's posts. Anything the agent can read, a hostile page might talk it into repeating. This post is about giving an agent the power to run your blog without giving it the key itself.

The short version:

  1. Give the agent an editor key, never the owner key. An editor can write and publish; it cannot make or revoke keys.
  2. If the agent runs somewhere else (ChatGPT, Claude, a cloud tool), connect it with the remote MCP server and OAuth. You sign in on Bloggo's page; the agent only gets a token.
  3. If the agent runs on your computer, either connect it the same way, or give it a small wrapper around curl, or a proxy between it and the internet, that adds the key to requests for api.bloggo.blog. The agent calls the API without a key, so there is nothing to leak.

What can go wrong

A key leaks in three ordinary ways:

You can't make an agent immune to the second one. What you can do is make sure there is no key in reach, and that whatever the agent holds can be cut off in one request and can do little damage.

Use the smallest key

Bloggo has three roles you can hand out for writing: owner, editor and, for readers of private blogs, reader keys. For an agent you want an editor key:

curl -X POST https://api.bloggo.blog/v1/keys \
  -H "Authorization: Bearer $OWNER_KEY" -H "Content-Type: application/json" \
  -d '{"name":"laptop-agent","role":"editor"}'

The answer has the new key, shown once. You can also make one without a terminal: sign in at api.bloggo.blog/sign-in with the owner key, and use Make an editor key on the dashboard. It asks for a name and, if you want, a date on which the key stops working. Leave the date empty and the key lasts until you revoke it. From the API the same choice is expires_in_days or expires_at in the request above.

An editor key can read, write, publish and delete posts, upload images and read stats. It cannot create or revoke keys, manage reader keys, invite anyone, change the owner email, or make access links to anything but a draft. If it leaks, you revoke it with "Revoke" on the dashboard or DELETE /v1/keys/:id, and nothing else changes.

Make one key per agent and name it after the agent, so you know which to revoke. Keep the owner key out of every agent's reach: it is the one that can make more keys.

About invitation keys

An invitation key (bgo_inv_…) works once, to make a blog. What it makes is a blog with an owner key, and whoever redeems the invitation sees that key. So redeem it yourself, not through an agent:

Remote agents: sign in with OAuth

ChatGPT, and any other agent that can talk to a remote MCP server, doesn't need your key at all. Bloggo's server is at:

https://api.bloggo.blog/mcp

When the client first connects, Bloggo answers that it needs a sign-in, and the client opens a Bloggo page in your browser. You paste your editor key there, on api.bloggo.blog, and the client gets back a token that Bloggo made for it. The model sees the tools' results and never the token or the key.

What the connection is, in plain terms:

Bloggo's server follows the standard pieces of the MCP authorization flow: it publishes its sign-in settings at /.well-known/oauth-authorization-server, lets a client register itself (RFC 7591), requires PKCE, and accepts http://localhost return addresses so command-line tools work. That means this isn't only for ChatGPT. Any client that supports remote MCP servers with OAuth should work, and the common ones do, among them Claude (the web and desktop apps' connectors, and Claude Code), Cursor, VS Code and Gemini CLI. The steps are always the same: add a remote server with that URL, let the client open the browser, paste the key on Bloggo's page, press Connect. In Claude Code, for example:

claude mcp add --transport http bloggo https://api.bloggo.blog/mcp
# then, inside Claude Code, run /mcp and choose to authenticate bloggo

Menus and flags change, so follow your client's own instructions for "add a remote MCP server". The ChatGPT guide shows the whole thing with screenshots.

Clients that can't register themselves or can't open a browser (a script, a server job) can't use this route. For those, see the proxy below.

Local agents: let a proxy hold the key

An agent on your own computer, one that runs curl or a script against the REST API, is the hard case. The usual advice is an environment variable, but that puts the key where the agent can print it. A better arrangement is that the agent never has the key, and a separate program adds it on the way out.

agent  --(no key)-->  local proxy  --(Authorization: Bearer …)-->  api.bloggo.blog

The proxy matches on the destination host. A request for api.bloggo.blog gets the header; a request for any other host passes through untouched, so the key can't be sent to a site the agent was tricked into calling. Whatever the agent writes in its own Authorization header for Bloggo is overwritten.

Which proxy

Several tools do exactly this: the secret stays with the proxy, and the agent gets nothing or a placeholder. Most are young projects, so check their state before you rely on them:

Tool How it works Notes
mitmproxy with a ten-line script A general-purpose HTTPS proxy. A small addon adds the header for one host. Mature and widely used. Nothing agent-specific, so you write the script (below).
credproxy Wraps the agent's command, intercepts HTTPS, swaps a placeholder token for the real one by host. Secrets come from 1Password. Go, MIT, new. Good if your secrets already live in 1Password.
Aegis A local proxy with an encrypted vault and an allowed-host list per credential. Apache 2.0, early. The agent has to send requests to localhost:3100/<service>/… instead of the real address.
agent-airlock A reverse proxy that holds the keys and gives the agent a revocable proxy token. MIT, very early. The agent's base URL must point at the proxy.
Docker Sandboxes Runs the agent in a sandbox; the host proxy swaps a placeholder for the real credential. Documented for built-in providers; check whether yours can be added.

For Bloggo I'd start with mitmproxy. It is the most mature of these, the agent needs no changes (no rewritten URLs, no placeholder tokens), and the whole policy fits on one screen.

A working example with mitmproxy

Install it (pipx install mitmproxy) and save this as inject.py:

"""Adds the Bloggo key to requests for api.bloggo.blog, and only those."""
import os
from mitmproxy import http

HOST = "api.bloggo.blog"
KEY = os.environ["BLOGGO_KEY"]

def request(flow: http.HTTPFlow) -> None:
    if flow.request.host == HOST:
        flow.request.headers["Authorization"] = f"Bearer {KEY}"

Start the proxy in a terminal. This is where the key lives, and it is the only place:

BLOGGO_KEY=bgo_… mitmdump -p 8080 -s inject.py

Then start the agent in another terminal, pointed at the proxy and told to trust the proxy's certificate (mitmproxy creates one in ~/.mitmproxy/ the first time it runs). The agent's environment has no key in it:

export HTTPS_PROXY=http://127.0.0.1:8080
export SSL_CERT_FILE=$HOME/.mitmproxy/mitmproxy-ca-cert.pem        # curl, Python
export NODE_EXTRA_CA_CERTS=$HOME/.mitmproxy/mitmproxy-ca-cert.pem  # Node
claude   # or whatever agent you run

Now curl https://api.bloggo.blog/v1/posts works from the agent with no key at all.

Trust the proxy's certificate only in the agent's own environment, with those variables. Don't install it for your whole system.

Even simpler: a wrapper around curl

If the agent only needs to call the REST API, you don't need a proxy. Write a small script that runs curl for it, always against api.bloggo.blog, and adds the key itself. Save it as bloggo-curl, make it executable (chmod +x bloggo-curl) and put it on the PATH:

#!/usr/bin/env bash
# bloggo-curl: call the Bloggo API with the key added for you.
#   bloggo-curl /v1/posts
#   bloggo-curl /v1/posts -X POST -H "Content-Type: application/json" -d @post.json
set -euo pipefail

path=${1:-}; shift || true
case $path in /*) ;; *) echo "usage: bloggo-curl /v1/path [curl options]" >&2; exit 2;; esac
for a in "$@"; do
  case $a in *://*) echo "bloggo-curl only talks to api.bloggo.blog" >&2; exit 2;; esac
done

# The key comes from $BLOGGO_KEY, or else from a private file.
key=${BLOGGO_KEY:-$(cat "${BLOGGO_KEY_FILE:-$HOME/.config/bloggo/key}")}

# --config <(...) keeps the key out of the command line, so `ps` never shows it.
exec curl -sS --config <(printf 'header = "Authorization: Bearer %s"\n' "$key") \
  "https://api.bloggo.blog$path" "$@"

Keep the key in a file only you can read (umask 077; mkdir -p ~/.config/bloggo; cat > ~/.config/bloggo/key), or in $BLOGGO_KEY if you prefer. Then tell the agent: "To use Bloggo, run bloggo-curl /v1/posts. Never ask for or print the key." The key never appears in the agent's commands, its history or its chat, and the script refuses any address other than Bloggo's, so a tricked agent can't point it at another site.

I tried the script against a local test server: the key arrived from the file and from the variable, an address in the arguments was refused, and ps didn't show the key. The wrapper is a convenience, not a lock. An agent that can read the key file, or the script's environment, can read the key, so keep the file where the agent can't reach it (see below).

What this does and doesn't protect

If all this feels like a lot for one blog, there is a simpler local route: use the OAuth connection from the section above on your local agent too. Claude Code, Cursor and VS Code run on your computer and still sign in that way, with a token that lives in the client rather than in the model's reach.

Which route to pick

And whichever you choose: one key per agent, editor not owner, and if you ever think a key was exposed, revoke it. It takes one request, and making a new one takes another.