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.
/ai-skill-setup.md instead of the HTML — same content, about a tenth of the tokens. For application hosting or publication settings, go directly to Apps; the document-sharing routes and defaults below are separate. For document sharing, decide which of these you are, then follow only that route:
pitchstation.ai/?share=1, or run a curl you prepare with a $PITCHSTATION_TOKEN placeholder (§10) — never paste their real token into the chat.
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.
Once it is set up, this is the everyday sentence — no flags, no jargon:
PitchStation authenticates agents with an API token: a string starting pst_ that acts as you, within the scopes you give it. An AI cannot create one for you — it lives behind your sign-in.
pitchstation.ai/login.html — “Continue with Google” provisions instantly, or use email + password.
pitchstation.ai/tokens.html → Access: AI skill: publish only → Create token. That preset publishes, replaces, lists and reads comments, and nothing else. For the MCP server's wider toolset tick the scopes you want its tools to have; it lists only the tools the token can use. Apps need the Apps preset (see Apps).
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.
pst_… value immediatelyIt is shown once. Close the page without copying it and there is no recovery — you delete it and create another. That costs nothing.
A .env file beside the skill (Route A, §04) or the MCP host's env block (Route B, §05). Both are covered below.
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:
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.
Two ways to connect. They do the same things; they differ in what has to be installed on the machine.
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.
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 — portable | Route B — MCP | |
|---|---|---|
| Needs | python3 only | Node 18+ and an MCP host |
| Install | 2 files + .env | 1 file + host config + restart |
| How the AI calls it | shell: python3 pitchstation.py publish … | tools: pitchstation_publish, _list, _replace, _revoke… |
| Covers | publish, bundles, teams, replace, edit, comments, revoke | all of that plus spaces, invoices, expense packs |
| Works in web chatbots | No — both need a machine that can run commands | No — 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.
Everything lives in one directory. Run this on the machine your assistant works on:
Then the config file beside them — this is the whole configuration surface (see §06):
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.
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.One dependency-free Node file. Download it somewhere stable (not a temp folder — the host launches it by absolute path on every start):
Register it. In Claude Code, one command does it; --scope user offers the tools in every folder, not only the one where you run it:
Any other MCP host takes the same thing as JSON — use an absolute path, ~ is not expanded 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:
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:
ChatGPT → Explore GPTs → Create. Name it something like "PitchStation Publisher".
Configure → Actions → Import from URL → https://pitchstation.ai/api/openapi-shares.json. Publish, list, replace, revoke, recipients, and invitations all come with it.
Authentication → API Key → Auth Type Bearer → paste your pst_… token from /tokens.html. The key is stored by the GPT, never shown in chat.
Copy this into the GPT's Instructions box — it carries the house rules the API cannot enforce:
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.
There are two layers, and mixing them up is the usual first-run confusion.
Exactly two settings exist: where to connect, and the token. Anything else you have seen in an example is a per-call option, not a config key.
| Key | Default if unset | What it does |
|---|---|---|
PITCHSTATION_URL | https://pitchstation.ai | Which deployment to talk to. Always the bare domain: www.pitchstation.ai only redirects browsers, and app.pitchstation.ai is the CDN's origin name, not a place to sign in or call. Skill copies downloaded before 2026-09-26 defaulted to www — re-download them or set this value. Change the host only when self-hosting. |
PITCHSTATION_SHARE_TOKEN (Route A)PITCHSTATION_MCP_TOKEN (Route B) | — (required) | Your pst_ token. Each client reads its own name first, then the shared PITCHSTATION_TOKEN (older setups keep working), so one machine can hold a narrowly scoped token per tool. 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-insensitively — pitchstation_share_token and PITCHSTATION_SHARE_TOKEN both resolve, so a token already exported in your shell under either spelling is picked up as-is.
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:
expires_in: a link that dies on its own is one fewer thing to remember to revoke.| Setting | Built-in default | Recommended above |
|---|---|---|
| Access | Route A CLI: account (sign-in required) · MCP: whatever the model chooses | link + password — works for people without accounts |
| Password | none | always, explicit string |
| Expiry | never | 30 days |
| Downloads | off | off — the tracked link stays the record |
| Watermark | off | off, unless the document is sensitive |
| Auto-translate | on 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.
Do this once, end to end. It costs nothing and catches a wrong token, a wrong URL, and a misunderstood default in one pass.
On Route B, the same four steps read: pitchstation_list → pitchstation_publish → open in a private window → pitchstation_revoke.
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.
| Rule | Why |
|---|---|
| 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 password | A 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-publish | Re-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 team | Two 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 access | Adding 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 everyone | Not a draft action. Widening an existing share is silent by comparison; publishing is not. |
| 10 MB per payload | Base64 images inside HTML blow through it fast. Strip them or publish as PDF. |
| What you see | What it is |
|---|---|
no API token | No .env beside pitchstation.py and no PITCHSTATION_SHARE_TOKEN or 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 401 | Token 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 403 SCOPE_REQUIRED | The token does not have the scope that action needs; the response names it in required. Create a token with that scope (or a preset that has it) — tokens cannot be widened after the fact. |
HTTP 401 TOKEN_EXPIRED | The token passed its expiry date. Create a replacement at /tokens.html; you are emailed two weeks before it happens. |
HTTP 403 ORIGIN_NOT_ALLOWED | A server only token was used from a web page. Use a token with Server only switched off for that page, and nothing else. |
| HTTP 413 | Over the 10 MB payload cap. Strip embedded base64 images, or publish as PDF. |
HTTP 409 TEAM_AMBIGUOUS | Two of your teams share that handle. Nothing was shared. Retry with @owner/handle. |
400 on --acl / aclUsernames | Allowlists are account-mode only. Drop the flag or switch to --access account. |
| The AI ignores the skill | It 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 appear | Relative 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. |
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:
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.
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:
Create the token for this page with the AI skill: publish only preset and Server only switched off: tokens are server only by default, and refuse requests made from a web page.
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 specs — shares, invoices, websites, apps (hosted prototypes; needs the admin-granted app.deploy capability), dev instances (repo-sized cloud workbenches for AI agents; needs devbox.manage; skill at /downloads/pitchstation-devbox-SKILL.md); the human reference is /api-docs.html (sign-in required).
For running prototypes, install create-pitchstation-app. It covers new projects, content deploys and domain/policy-only changes to existing apps, and drives the standalone Apps client below. The MCP server (Route B) also has six Apps tools — list, status, logs, deploy, operation, readiness — when the client sits in the same folder. It needs an account with the app.deploy capability, a token created with the Apps preset, and a host you may use (your own, or a shared host an admin has opened to teammates); a document-sharing token or installed skill does not grant that capability.
Then install the Apps client — one dependency-free Node file (Node 20+) — and its ps-apps launcher. Put the client beside the MCP server if you use Route B, so pitchstation_app_deploy can package directories:
Restart the assistant after installation. The Apps client reads PITCHSTATION_APPS_TOKEN (then the shared PITCHSTATION_TOKEN) from its environment only; keep the token in ~/.pitchstation/.env, not in every shell, and never put tokens or preview passwords in command-line flags. The ~/.pitchstation/ps-apps launcher loads that file for one command, which also works in Codex, where token variables are hidden from commands. Simpler: tell the assistant to follow the skill's Setup section, which installs both. The MCP server reads its own PITCHSTATION_MCP_TOKEN (then PITCHSTATION_TOKEN), not the Apps client's variable.
# Examples use an app you own: ~/.pitchstation/ps-apps help ~/.pitchstation/ps-apps show --app 7 ~/.pitchstation/ps-apps deploy --app 7 --source /absolute/path/to/project # dry run ~/.pitchstation/ps-apps domains plan --app 7 --file domains.json ~/.pitchstation/ps-apps domains verify --app 7 --plan PLAN_UUID # After authorization, DNS verification and review of this exact plan/ETag: ~/.pitchstation/ps-apps domains apply --app 7 --plan PLAN_UUID --etag '"app-7-v2"' --key domains-20260927-01 ~/.pitchstation/ps-apps operation --app 7 --operation OPERATION_UUID --watch
Choose the right operation. Domain/access/expiry changes do not rebuild or restart an unchanged app. Env PATCH does restart the current release; env PUT only replaces stored keys. Content deployment packages and builds source. For an app that must be private from its first request, create it with create --private --password-file FILE, or deploy with --password-file (client), password_file (MCP) or previewPassword in a protected REST body; the file must be readable by you alone. Planning is not live activation, and HTTP 202 acceptance is not readiness. A public app passes readiness only after its owner declares applicationPolicy: "none" with a policy plan/apply (details).
Status in this release: the Singapore host is the shared host for teammates with app.deploy (ARM64: images must build for ARM64; Compose rules and per-app budgets enforced; 3 apps each); the Oregon host is the platform account's own (rules and budgets only warn); a user can also reserve an isolated host of their own, which an administrator launches; new apps use the managed route from their first deploy; six pre-September apps keep their original routes until their next domains or access change; custom-domain ownership proofs are implemented, first production use pending. Keep correct A/CNAME records and add each displayed _pitchstation.<hostname> TXT proof manually. Check /api/apps/publishing-capabilities and the owned app’s configuration. Existing passwords/expiry are preserved; document-share defaults above do not automatically apply to apps. Separate application login/expiry cannot be removed by a platform setting. Certificate email does not enable renewal alerts; monitoring/email remained off at rollout.
Apps documentation (markdown for agents) · user manual · Apps product page · Go-live workspace · Apps introduction · Human API reference · Apps OpenAPI.
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.