How to bring people onto the platform and scope exactly what they can touch — org roles, teams as permission sets, the operator/admin split, and the restricted machine accounts you should copy when you need a bot.
Registration
Registration is open. New users sign up at https://git.espoautos.com/user/sign_up (image CAPTCHA, internal accounts only — no external auth). Every new user can create organizations, and everything they create is private by default. If you want an invite-only instance, that is a Forgejo config switch (DISABLE_REGISTRATION) — the platform doesn't depend on either mode.
Add a person to an org
Membership flows through teams — a user is in an org because they're on one of its teams.
UI: open the org page → Teams in the sidebar → pick a team → Add Team Member. URL shape: https://git.espoautos.com/org/<org>/teams/<team-name> — e.g. https://git.espoautos.com/org/homestead/teams/owners.
API (exactly what the platform itself uses):
# find the team id
curl -s https://git.espoautos.com/api/v1/orgs/<org>/teams?limit=50 -H "Authorization: token $TOKEN"
# add the member (422 = already a member; retry on 5xx only)
curl -X PUT https://git.espoautos.com/api/v1/teams/<team_id>/members/<username> -H "Authorization: token $TOKEN"
For a single repo instead of a whole org: repo Settings → Collaborators, or PUT /api/v1/repos/{org}/{repo}/collaborators/{username} with {"permission": "read"|"write"|"admin"}.
One thing to internalize: org membership is not repo permission. Authorization checks run against repo-level permission (owner | admin | write | read | none), which teams and collaborator grants confer. That's the keystone of the access model.
Teams are permission sets
Every org has a built-in Owners team (full control). Other teams carry a level — read, write, or admin — optionally narrowed to specific repo units (repo.code, repo.issues, repo.packages, ...) via per-unit overrides.
The platform's own pattern is the sharpest example: each org gets a registry-writers team that can push container images and Helm charts but touch nothing else —
{
"permission": "read",
"units": ["repo.packages"],
"units_map": { "repo.packages": "write" },
"includes_all_repositories": true,
"can_create_org_repo": false
}
The units_map is what keeps it from carrying any repo permission. Package-write is only delegatable through an org team at all — which is why every app is org-owned. Copy this shape whenever you need a narrowly scoped grant: pick the unit, map only that unit to write, leave the base permission at read.
Operator vs admin
Two hats, enforced by construction, never worn at once. The operator is a normal user — not site admin, zero access to any plat/* system repo — who is Owner of every product org and steers the fleet via labels (agent-work, ultracode), the /flow/:org API, and mission-control issues on their own <login>/fleet repo. The admin is plat: Forgejo site admin, owner of the platform-altering repos (plat/agents, plat/mcp, plat/gitops, plat/mitosis, plat/_app-template, plat/ci-runner); the dispatcher runs as plat, and you log into it only for admin duties. To make someone an operator: add their login to the dispatcher's PLAT_OPERATORS env, add them to the Owners team of the product orgs, and create their <login>/fleet repo — never site-admin them. Product work never needs admin, and platform changes never ride an operator account. See Steering the Fleet for what the operator seat does day to day.
Machine users: the restricted-bot model
When automation needs a credential, mint a restricted user (Forgejo restricted: true — sees only what it is explicitly granted) with the narrowest token scope that works. Two live examples to copy:
| Bot | Access | Token scope |
|---|---|---|
ci-bot |
write on plat/mitosis; read on gitops/agents/mcp/_app-template and a few others |
write:repository,write:issue |
registry-bot-<org> |
member of that org's registry-writers team only |
write:package only |
Registry bot passwords are derived (HMAC(SESSION_KEY, "registry-bot:"+org)), never stored. Agents themselves don't even get a user: each run pushes with a per-run, single-repo SSH deploy key that's deleted on exit — an agent physically cannot write outside its assigned repo. The Security Model page covers why this holds under adversarial pressure.
What a new member sees
Private-first, so: their own orgs and repos, the public seed repos on /explore, and nothing else. Every org and app repo is born private (DEFAULT_ORG_VISIBILITY=private, DEFAULT_PRIVATE=private); other tenants' work is invisible unless a team or collaborator grant says otherwise. From there, their first stop is Build Your First App — and if they need credentials for an app, Secrets and Config shows the self-serve channel (secrets go in repo settings, never in issues or comments).
For everyone
Experience
Operate
Under the hood
Grow
This wiki ships inside plat/mitosis (wiki/) — edit it there, not here. Grown by the platform it describes.