Setup guide · send this link to anyone

Connect your AI to PitchStation

Your assistant — Claude Code, Codex, Cline, Kimi, or anything that can run a shell command — can publish a document and hand you back a secure, tracked link: password, expiry, per-page analytics, revoke. This page is the whole setup. You can paste this URL straight into your AI and let it install itself; the one step it cannot do for you is creating the token.

Jump to 01 · Hand it to your AI 02 · Get your token 03 · Pick a route 04 · Route A · portable 05 · Route B · MCP 05b · Route C · ChatGPT 06 · Default config 07 · Verify 08 · House rules 09 · Troubleshooting This page as markdown ↓
If you are the AI reading this: fetch /ai-skill-setup.md instead of the HTML — same content, about a tenth of the tokens. First decide which of these you are, then follow only that route:
· You can run shell commands (Claude Code, Codex CLI, Cline…) → §02 → §03 → (§04 or §05) → §06 → §07 in order. Do not skip §06.
· You are ChatGPT → your sandbox has no network and your browser tool cannot send auth headers — do not attempt to call the API yourself, and do not loop retrying. Publishing works only through a GPT Action (§05b), or hand off per the next line.
· You can do neither → produce the finished HTML as a downloadable file and give the user two options: drag it into the Share composer at pitchstation.ai/?share=1, or run a curl you prepare with a $PITCHSTATION_TOKEN placeholder (§10) — never paste their real token into the chat.
Anything on this page that is a placeholder is written like <THIS> — ask the user, never invent it.

§01Hand it to your AI

Open your assistant in the folder you work in, and paste this. It reads the guide, installs itself, and writes down the defaults so future sessions behave the same.

Setup promptSet yourself up to publish documents to PitchStation. 1. Read https://www.pitchstation.ai/ai-skill-setup.md — that is the setup guide, written for you. Follow it in order and do not improvise around it. 2. Install the portable skill (Route A, §04) unless I already run an MCP host — ask me if you are unsure which. 3. I will give you the Personal Access Token when you ask for it. Do not guess a token, do not skip the .env step, and do not print the token back to me. 4. Apply the default configuration in §06 — write it into my CLAUDE.md so it survives this session. 5. Verify with the `list` command in §07, then tell me in two lines what you can now do and where my token ended up.

Once it is set up, this is the everyday sentence — no flags, no jargon:

Everyday usePublish <the file, or "the report you just wrote"> to PitchStation and give me the link and the password.
🔐 Nothing publishes silently. Every share is created under your account, appears on your Links page, and can be revoked from there. If your assistant publishes something you did not want public, revoke it and the URL dies immediately.

§02Get your token — the one step only you can do

PitchStation authenticates agents with a Personal Access Token (PAT): a string starting pst_ that acts as you. An AI cannot create one for you — it lives behind your sign-in.

Sign in, or create a free account

pitchstation.ai/login.html — “Continue with Google” provisions instantly, or use email + password.

Open the token page

pitchstation.ai/tokens.htmlNew token.

Name it after the machine and the agent

claude-code-macbook, codex-work-laptop, ci-publish. The name is the only way you will know which one to revoke later — “token 3” tells you nothing in six months.

Copy the pst_… value immediately

It is shown once. Close the page without copying it and there is no recovery — you delete it and create another. That costs nothing.

Put it where the agent reads it

A .env file beside the skill (Route A, §04) or the MCP host's env block (Route B, §05). Both are covered below.

⚠️ A PAT is not scoped — it can do anything you can do, on your account, under your name. Treat it exactly like a password:
  • Never commit it. Never put it in a document, a deck, or a share.
  • Never paste it into a public chat window or a bug report.
  • Leaked or unsure? Delete it at /tokens.html and make a new one. Nothing else breaks — your existing links keep working; you just re-configure the one agent.

Handing the token over without it landing in a transcript

If you would rather your assistant never sees the string, run this yourself in your own terminal before the AI gets to the .env step. read -rs hides the input and keeps it out of your shell history:

You run this — the token stays off-screenmkdir -p ~/.claude/skills/share-to-pitchstation read -rs -p "Paste your PitchStation token: " T && \ printf 'PITCHSTATION_URL=https://www.pitchstation.ai\nPITCHSTATION_TOKEN=%s\n' "$T" \ > ~/.claude/skills/share-to-pitchstation/.env && \ chmod 600 ~/.claude/skills/share-to-pitchstation/.env && unset T && echo " .env written"

Then tell the AI “the .env is already written, skip that step”. Pasting the token to the agent instead is a reasonable trade-off too — it is revocable in two clicks — but the file above is the tidier habit.

§03Pick a route

Two ways to connect. They do the same things; they differ in what has to be installed on the machine.

🐍
Route A — portable skill

Recommended. Two files and a .env. Needs only python3 (already on every Mac and Linux box). No Node, no MCP host, no restart. Works in Claude Code CLI, the desktop app, and any session with shell access.

🔌
Route B — MCP server

A single Node 18+ file registered with an MCP host. Choose this if you already run MCP (Claude Code with other MCP servers, Cline, Continue) and want native tools rather than shell commands.

Route A — portableRoute B — MCP
Needspython3 onlyNode 18+ and an MCP host
Install2 files + .env1 file + host config + restart
How the AI calls itshell: python3 pitchstation.py publish …tools: pitchstation_publish, _list, _replace, _revoke
Coverspublish, bundles, teams, replace, edit, comments, revokeall of that plus spaces, invoices, expense packs
Works in web chatbotsNo — both need a machine that can run commandsNo — see §10 for browser-only assistants

Installing both is fine and quite common: the MCP tools when the host has them, the portable script everywhere else. The skill files know about each other — the portable one defers to the MCP tools when they are present.

§04Route A — the portable skill

Everything lives in one directory. Run this on the machine your assistant works on:

Install — Route Amkdir -p ~/.claude/skills/share-to-pitchstation cd ~/.claude/skills/share-to-pitchstation curl -fsSL https://www.pitchstation.ai/downloads/share-to-pitchstation-SKILL.md -o SKILL.md curl -fsSL https://www.pitchstation.ai/downloads/pitchstation.py -o pitchstation.py

Then the config file beside them — this is the whole configuration surface (see §06):

~/.claude/skills/share-to-pitchstation/.envPITCHSTATION_URL=https://www.pitchstation.ai PITCHSTATION_TOKEN=pst_your_token_here
Lock it down & checkchmod 600 ~/.claude/skills/share-to-pitchstation/.env python3 ~/.claude/skills/share-to-pitchstation/pitchstation.py list

list printing your shares (or an empty list on a new account) means you are connected. Restart the assistant so it picks up SKILL.md, and it will use the script on its own from then on.

📁 Self-hosting? Point PITCHSTATION_URL at your own deployment — http://localhost:3018 in development. The env var beats the .env file, so you can override it for one command without editing anything.

§05Route B — the MCP server

One dependency-free Node file. Download it somewhere stable (not a temp folder — the host launches it by absolute path on every start):

Install — Route Bmkdir -p ~/.pitchstation curl -fsSL https://www.pitchstation.ai/downloads/pitchstation-mcp-server.js \ -o ~/.pitchstation/pitchstation-mcp-server.js

Register it. In Claude Code, one command does it:

Register with Claude Codeclaude mcp add pitchstation \ --env PITCHSTATION_URL=https://www.pitchstation.ai \ --env PITCHSTATION_TOKEN=pst_your_token_here \ -- node ~/.pitchstation/pitchstation-mcp-server.js

Any other MCP host takes the same thing as JSON — use an absolute path, ~ is not expanded here:

MCP host config{ "mcpServers": { "pitchstation": { "command": "node", "args": ["/absolute/path/to/pitchstation-mcp-server.js"], "env": { "PITCHSTATION_URL": "https://www.pitchstation.ai", "PITCHSTATION_TOKEN": "pst_your_token_here" } } } }

Restart the host. Ask your assistant to run pitchstation_list — if it answers, you are connected. Optionally add the matching skill file, which teaches it the conventions the raw tool descriptions do not carry:

Optional — the MCP skill filemkdir -p ~/.claude/skills/publish-to-pitchstation curl -fsSL https://www.pitchstation.ai/downloads/publish-to-pitchstation-SKILL.md \ -o ~/.claude/skills/publish-to-pitchstation/SKILL.md

§05bRoute C — ChatGPT (GPT Actions)

ChatGPT cannot use Route A or B: its sandbox has no network access at all, and its browsing tool is GET-only and cannot send an Authorization header. The one way ChatGPT can genuinely publish is a custom GPT with an Action — a one-time, five-minute setup:

1 · Create the GPT

ChatGPT → Explore GPTs → Create. Name it something like "PitchStation Publisher".

2 · Import the Action

Configure → Actions → Import from URLhttps://www.pitchstation.ai/api/openapi-shares.json. Publish, list, replace, revoke, recipients, and invitations all come with it.

3 · Set authentication

Authentication → API Key → Auth Type Bearer → paste your pst_… token from /tokens.html. The key is stored by the GPT, never shown in chat.

4 · Paste the instructions

Copy this into the GPT's Instructions box — it carries the house rules the API cannot enforce:

GPT instructionsYou publish documents to PitchStation via the imported Action and return tracked /s/ links. - Default to access:"account". For external audiences use access:"link" with expires_in (e.g. "10d") and password:true, and give the user BOTH the full url and the password from the response. - To UPDATE a published document, call replaceShareContent on its slug — NEVER publish a second copy; that mints a new URL and splits analytics. - aclUsernames only with access:"account"; recipients only with access:"recipient" (each address gets its own magic link, emailed automatically). - Never reveal the API key. Never invent slugs — list shares to find one. - If an upload is over ~10 MB the API returns 413: strip embedded base64 images first, or publish as PDF.

Everyday use is then just: "publish this HTML to PitchStation, link + password", pasted into that GPT. Plain ChatGPT (no Action): it cannot call any API — see §10 for the honest fallback.

§06Default configuration

There are two layers, and mixing them up is the usual first-run confusion.

Layer 1 — connection settings (a file)

Exactly two keys exist. Anything else you have seen in an example is a per-call option, not a config key.

KeyDefault if unsetWhat it does
PITCHSTATION_URLhttps://www.pitchstation.aiWhich deployment to talk to. Change only when self-hosting.
PITCHSTATION_TOKEN— (required)Your pst_ PAT. No default; missing it is a hard error, not a fallback to anonymous.

Route A reads them from .env next to pitchstation.py; a real environment variable wins over the file. Route B reads them from the MCP host's env block. In every case the key is matched case-insensitivelypitchstation_token and PITCHSTATION_TOKEN both resolve, so a token already exported in your shell under either spelling is picked up as-is.

Layer 2 — publishing defaults (instructions, not a file)

How a share comes out — who can open it, whether it has a password, when it expires — is decided per call. There is no config file for it, which means if you do not state your defaults, your assistant picks them, and its built-in fallback is not what most people want. Paste this into ~/.claude/CLAUDE.md (or your host's equivalent memory file) and it applies to every session on that machine:

Paste into ~/.claude/CLAUDE.md## PitchStation — publishing defaults If the audience is ME ("share to me"), MY TEAM, or named registered users: access account (+ teams), NO password — signing in is the gate. Otherwise — outside audience or none named — every share uses: - access: link - password: an explicit memorable string you choose, shaped <topic>-<word>-<MMDD> - expires_in / --expires: 30d - downloads: off, watermark: off - Always report back: the FULL url including ?k=..., the password, and the expiry. Rules, in order of how often they get broken: 1. An email address in my request does NOT mean recipient mode. Publish a link + password and give me both to forward. Use recipient mode only if I say "magic link" or "email it to them directly". 2. Never hand me a share whose password you do not know. Pass an explicit string; if you ever let the server generate one, read it out of the result and tell me — a password neither of us has means a dead link. 3. Updating something already shared: use replace (same URL, same password, same analytics). Never publish a second copy. Revoke only when the link should genuinely die. 4. Ambiguous team name: show me the candidates and stop. Never pick one. 5. Ask before publishing anything I did not explicitly ask you to publish.
⚠️ Why default to a password. A link-mode share with no password is readable by anyone who ever receives the URL — forwarded, pasted into a group chat, quoted in an email thread. The password costs one extra line in the message you send and turns “anyone with the URL” into “the person I gave it to”. Its natural pair is expires_in: a link that dies on its own is one fewer thing to remember to revoke.

The defaults you are overriding

SettingBuilt-in defaultRecommended above
AccessRoute A CLI: account (sign-in required) · MCP: whatever the model chooseslink + password — works for people without accounts
Passwordnonealways, explicit string
Expirynever30 days
Downloadsoffoff — the tracked link stays the record
Watermarkoffoff, unless the document is sensitive
Auto-translateon for HTML shares (viewer's language picker)on — but turn it off when the exact wording is the point, e.g. a contract

Common variations worth naming in your own defaults block instead of re-typing every time: “investor link” = link + password + 30d + no downloads; “team doc” = account mode + your team handle, no expiry; “one-time” = link + password + max-views 1.

§07Verify it actually works

Do this once, end to end. It costs nothing and catches a wrong token, a wrong URL, and a misunderstood default in one pass.

Smoke test — Route Acd ~/.claude/skills/share-to-pitchstation echo '<h1>Hello from PitchStation</h1>' > /tmp/ps-test.html # 1. Connection: prints your shares, or an empty list. Anything else is §09. python3 pitchstation.py list # 2. Publish with the house defaults: python3 pitchstation.py publish /tmp/ps-test.html \ --title "Setup test" --access link --password "setup-test-0101" --expires 24h # 3. Open the printed URL in a private window: it must ASK FOR THE PASSWORD. # If it opens straight to the document, the password did not apply — stop # and re-read the command before you publish anything real. # 4. Clean up: python3 pitchstation.py revoke <slug-from-step-2>

On Route B, the same four steps read: pitchstation_listpitchstation_publish → open in a private window → pitchstation_revoke.

Step 3 is the one that matters. Everything else fails loudly. A share that quietly published without the protection you asked for is the only failure mode that looks like success — check it once, on a document that does not matter.

§08House rules for the AI

These are the mistakes that actually happen on a first run. They are in §06's defaults block too — repeated here because each one has a cost worth knowing.

RuleWhy
Email ≠ mode“Share this with bob@x.com” names the reader, not the access mode. Recipient mode emails per-person magic links, which is rarely what was meant. Publish link + password; the user forwards both.
Know the passwordA password neither the user nor the agent can state is a dead link — the document is published and unreadable. Pass an explicit string; if the server generates one, read it out of the result and say it.
Replace, don't re-publishRe-publishing mints a second URL and splits the analytics across two links, so neither tells the truth about who read what. replace keeps the URL, password, and history.
Never resolve an ambiguous teamTwo teams can share a handle. A document sent to the wrong team cannot be recalled. The API refuses with a candidate list — surface it and ask.
Relay retroactive accessAdding a member to a team grants them everything the team already holds. The API returns the count; say the number out loud — it is correct behaviour and still a surprise.
Publishing to a team emails everyoneNot a draft action. Widening an existing share is silent by comparison; publishing is not.
10 MB per payloadBase64 images inside HTML blow through it fast. Strip them or publish as PDF.

§09Troubleshooting

What you seeWhat it is
PITCHSTATION_TOKEN not setNo .env beside pitchstation.py and no PITCHSTATION_TOKEN / pitchstation_token in the environment (any casing counts). Note the .env must sit in the same directory as the script — not the project you are working in.
HTTP 401Token missing or wrong. Re-copy it — a truncated paste looks identical to a valid one.
HTTP 403 “API token revoked”The token was deleted at /tokens.html. Create a new one and update .env / the MCP env block.
HTTP 413Over the 10 MB payload cap. Strip embedded base64 images, or publish as PDF.
HTTP 409 TEAM_AMBIGUOUSTwo of your teams share that handle. Nothing was shared. Retry with @owner/handle.
400 on --acl / aclUsernamesAllowlists are account-mode only. Drop the flag or switch to --access account.
The AI ignores the skillIt only loads at start-up. Restart the assistant. Check the file really is at ~/.claude/skills/<name>/SKILL.md — a nested extra folder is the usual culprit.
MCP tools never appearRelative path in the host config (use an absolute one), Node older than 18, or the host was not restarted. node ~/.pitchstation/pitchstation-mcp-server.js should start and wait, not exit with an error.

§10Other assistants, and no assistant at all

ChatGPT — set up the GPT Action once (§05b) and it publishes natively. Without an Action, plain ChatGPT structurally cannot call any API (no network in its sandbox, no auth headers in its browser tool) — the honest move is a handoff: it saves the HTML as a file and you either drag that into the Share composer, or run the command below with your own token in the environment:

Publish any HTML file — one commandcurl -X POST "https://www.pitchstation.ai/api/share?title=My+Report&access=link&expires=10d&password=auto" \ -H "Authorization: Bearer $PITCHSTATION_TOKEN" \ -H "Content-Type: text/html" --data-binary @report.html

That is the raw-body publish: the file is the body, the controls are query parameters (title, access, expires, password, max_views, watermark, downloads, recipients) — no JSON escaping, so any assistant can prepare it correctly.

Route D — the publish page (works from a ChatGPT canvas)

An assistant that cannot reach the network itself can still write a page that publishes from your browser. The API answers cross-origin for any request carrying Authorization: Bearer …, so this works from a ChatGPT canvas, an artifact, or an HTML file you open locally:

Ask any assistant for thisWrite a single self-contained HTML page that publishes the document below to PitchStation from my browser: - a password-type input for my PitchStation token (never hardcode a token), - a Publish button that POSTs to https://www.pitchstation.ai/api/share with header "Authorization: Bearer <token>" and JSON body { htmlContent, title, access: "account" }, - show the returned url as a clickable link, and clear the token field after.

Why this is safe: the token never leaves your browser except to pitchstation.ai, and cross-origin calls to our API can never carry your session cookies — a page like this can only act with a token you deliberately paste. Revoke any token at /tokens.html if you paste it somewhere you later think better of.

Other browser chatbots — claude.ai, Gemini in a tab — same story: no shell, no filesystem. Use the web app to publish, or run the setup on a machine with a terminal. They can read a share you send them, and review a document that has comments enabled.

Anything that can make an HTTP request — a script, a CI job, an agent framework nobody has heard of — skips all routes and calls the API directly with Authorization: Bearer pst_…. Machine-readable: /llms.txt and the OpenAPI spec at /api/openapi-shares.json; the human reference is /api-docs.html (sign-in required).

Sending a document out for review? That is the other half of the loop and needs no setup on the recipient's side: /ai-review-guide.html teaches their AI to read the document and post anchored comments. Between the two pages, a whole negotiation can run assistant-to-assistant with a human deciding at each end.

PitchStation · AI setup guide · pitchstation.ai · Related: tutorial · AI review guide · teams guide · llms.txt · this page as markdown: /ai-skill-setup.md