Instead of listing names on every document, make a team once and share with the team. Someone joins, they get what the team has. Someone leaves, they lose it everywhere β in one action, not a search through documents.
A team member is a PitchStation account, not an email address on a list. There is no way to check that a stranger controls an address, so an address alone cannot let someone in.
Two ways in:
POST /api/teams/:id/invites), send an invitation to their email with a personal note in your own words. They get a signup link; the moment they sign up and confirm that address, they join automatically β you get an email when it happens, they get the document list. Until then the invitation grants nothing: it appears in no access check, and you can revoke it any time from the pending list.curl -X POST https://pitchstation.ai/api/teams \
-H "Authorization: Bearer $PITCHSTATION_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"handle":"design","name":"Design team"}'
{ "success": true,
"team": { "id": 2,
"ref": "@leo_lin/design", β how anyone names it unambiguously
"members": 1, β you, automatically
"yourRole": "owner" } }The handle (design) is what you type. The ref (@leo_lin/design) is what you use when a plain handle would be ambiguous β see Β§05.
# by email β the usual case
-d '{"email":"simon@example.com"}'
# or by username, if you know it
-d '{"username":"simonruan"}'
curl -X POST https://pitchstation.ai/api/teams/2/members \
-H "Authorization: Bearer $PITCHSTATION_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"username":"simonruan"}'
{ "success": true,
"granted": 0,
"note": "This team has no documents yet." }If they have not signed up yet you get a clear refusal rather than a silent no-op:
{ "code": "USER_NOT_FOUND",
"error": "No account for \"nobody@example.com\". A team member must
already have an account: ask them to sign up at
/signup.html, then add them." }curl -X POST https://pitchstation.ai/api/share \
-H "Authorization: Bearer $PITCHSTATION_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"title":"Q3 plan", "access":"account",
"htmlContent":"β¦",
"teams":["design"]}'
{ "slug": "QeexQIP5",
"teams": { "teams": [ { "ref": "@leo_lin/design", "members": 2 } ] } }Everyone on the team is emailed automatically β they get the link, and it opens once they sign in. Nobody else's does. (Β§08 covers exactly when mail is and is not sent.)
access: "account" matters. A team member has to sign in for PitchStation to know they are one. If you leave it as a public link, anyone with the URL gets in and the team adds nothing.This is the part worth understanding before you do it. Adding a person gives them everything the team already has, not just what you share from now on. The response tells you the number:
{ "success": true,
"granted": 1,
"note": "That also gives them access to 1 document already
shared with this team." }That is the intended behaviour β it is what "share with the team" means β but it is a surprise the first time, so the count is in the response rather than something to discover later.
You do not have to republish. Widen it in place and the link, password, analytics and comments all carry forward:
curl -X PATCH https://pitchstation.ai/api/share/<slug>/access \
-H "Authorization: Bearer $PITCHSTATION_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"addTeams":["design"]}'
{ "promoted": { "from": "link", "to": "account",
"note": "a team member must sign in to be recognised as one" },
"grantedTo": 2,
"note": "This document is now open to 2 more people." }Going the other way needs no ceremony β {"removeTeams":["design"]} takes it back immediately.
You can be in your own design team and a client's design team at the same time. When you type just design, PitchStation refuses and asks which β it does not pick one:
{ "code": "TEAM_AMBIGUOUS",
"error": "\"design\" matches 2 teams. Name one exactly β nothing was shared.",
"candidates": [
{ "ref": "@leo_lin/design", "members": 1, "yourRole": "admin" },
{ "ref": "@bob/design", "members": 2, "yourRole": "member" } ] }Retry with the one you meant β "teams":["@bob/design"] or "teamIds":[3] β and it goes through.
| You do this | What happens |
|---|---|
| Remove them from the team | Access ends immediately, on every document the team granted. One action, not a search. |
| Archive the team | Every document it opened closes. Nothing is deleted; un-archiving restores access exactly. |
| Their comments | Stay. Deleting them would rewrite a discussion and orphan the replies to it. |
| The documents | Untouched. Removing a person changes who can read; it never changes what is there. |
This is the reason to use teams at all. Offboarding somebody becomes one call instead of remembering every document they were named on.
If you have PitchStation connected to Claude Code, Codex or another assistant, say what you want:
"Make a team called design, add simon@example.com, and share the Q3 plan with it."
Sharing something with a team emails the team. Changing who can reach a document you already shared does not, unless you ask.
| What you do | Who gets an email |
|---|---|
Publish with teams: | Every member, automatically. Putting a document in front of a team is telling them to read it. |
Widen an existing share β PATCH /access | Nobody, unless you add "notify": true. Widening is often just tidying up an allowlist, and mail nobody needs is mail everybody learns to ignore. |
| Add someone to a team | The new member β one email listing what the team grants them (retroactive access someone can't see is access they don't have). Pass "notify": false to skip; their own notification preference always wins. |
| Invite someone without an account | The invitee β an invitation with your quoted note and a signup link. When they sign up and confirm the address they join automatically, get the document list, and the inviter gets a "they joined" email. |
| Remove someone | Nobody. Their access simply ends. |
Send it again β POST /notify | Whoever you name: members, teams, viewers, recipients, owner. There is no βeveryoneβ default β a resend that reaches more people than you pictured is worse than one that reaches too few. |
# the miss most people hit: widened without notify, and now want to tell them
curl -X POST https://pitchstation.ai/api/share/<slug>/notify \
-H "Authorization: Bearer $PITCHSTATION_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"to":"members"}' # or ["members","owner"]
# announce a document the team can already reach β same URL, same analytics
curl -X PATCH https://pitchstation.ai/api/share/<slug>/access \
-H "Authorization: Bearer $PITCHSTATION_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"addTeams":["aip"],"notify":true}'
{ "grantedTo": 2,
"notified": ["yanglee@mobileinteractive.com"] }Two things it will not do: it never emails you unless you ask for owner, and it never re-sends a password β the password is stored hashed and cannot be recovered, so pass it on yourself. The response says so when the share has one.
Widening without notify now tells you it stayed quiet, rather than looking like a notification that failed:
{ "notified": [],
"notifyNote": "Nobody was emailed. Pass \"notify\": true to tell the 1 member,
or POST /api/share/<slug>/notify later." }notify.design team; that is why Β§05 exists.