Skip to content

Accounts, Teams & Permissions

Every bot belongs to a team, not directly to a user. pywa login (see the CLI Reference) covers day-to-day authentication; this page covers the account/team model underneath it, which today is API-only — there’s no pywa cloud teams ... command yet (see the CLI reference’s “Not built yet” section).

Terminal window
curl -X POST http://127.0.0.1:8000/auth/signup \
-H 'Content-Type: application/json' \
-d '{"email": "you@example.com", "password": "at-least-8-characters"}'

Returns a JWT access_token plus your default team id — signing up automatically creates a single-seat “Personal” team you own. pywa login (no --token) drives this same account system through its device-code flow instead of raw curl; either way you end up with the same JWT. Use the token as Authorization: Bearer <access_token> on every other request.

Terminal window
curl -X POST http://127.0.0.1:8000/auth/login \
-H 'Content-Type: application/json' \
-d '{"email": "you@example.com", "password": "..."}'

Your default team is single-seat by design — it can’t invite anyone (POST /teams/{id}/members/invite rejects it with 400). It exists so a solo user never has to think about teams at all: create a bot, it lands there, done. The moment you want a second person on a bot, create a real team:

Terminal window
curl -X POST http://127.0.0.1:8000/teams \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"name": "Acme Bots"}'

You’re automatically its Owner. Bots created afterward can be pointed at this team (or moved there later — see “Moving a bot between teams” below).

Three roles, each a strict superset of the one before it:

  • Member — read access to the team’s bots, deployments, logs, and metrics.
  • Admin — everything a Member can do, plus mutating actions: deploy, env vars, domains, rollback, connect/integrations, inviting and removing Members (not other Admins/Owners).
  • Owner — everything an Admin can do, plus changing roles, deleting the team, and removing Admins/other Owners. A team can never drop below one Owner — the API rejects any change that would (PATCH /teams/{id}/members/{user_id}/role, DELETE .../members/{user_id}).
Terminal window
curl -X POST http://127.0.0.1:8000/teams/$TEAM_ID/members/invite \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"email": "teammate@example.com"}'

Requires Admin or Owner. Returns a single-use, 24-hour invite token (also emailed — see control-plane/README.md for the pluggable email adapter). The invited person accepts it once they have an account of their own with that exact email:

Terminal window
curl -X POST http://127.0.0.1:8000/teams/$TEAM_ID/members/accept/$INVITE_TOKEN \
-H "Authorization: Bearer $THEIR_TOKEN"

They join as a Member; an Owner can promote them afterward with PATCH /teams/{id}/members/{user_id}/role.

Terminal window
curl -X POST http://127.0.0.1:8000/bots//transfer \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"destination_team_id": "'$DEST_TEAM_ID'", "confirm_slug": "my-bot"}'

Team-to-team only (not user-to-user), and you must already be a member of the destination team. Typing the bot’s own slug back as confirm_slug guards against transferring the wrong bot. The bot’s slug and config are untouched — its running container is looked up by slug, not team, so there’s no redeploy or downtime.

Terminal window
curl -X POST http://127.0.0.1:8000/auth/account/delete \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"confirm_email": "you@example.com"}'

Irreversible, hence typing your own email back rather than a single call with no confirmation. Blocked if you’re the sole Owner of a real (non-default) team that still has other members or bots — promote someone else or delete the team first. Your default team is removed automatically along with the account, as long as no bots remain on it.