No authentication. Both endpoints answer only what the SaaS administrator has opened with PATCH /v1/settings (freeSignup, paidSignup), the plans marked signup, and the shared domains marked signup (see api/openapi.yaml). Bodies must be application/json.
A free signup is active at once. A paid signup (solo or team plan) is created pending: it cannot sign in or receive mail until billing sets the tenant to active through PATCH /v1/tenants/{tenantId}.
Limits: 30 attempts per source address per hour, and signupsPerIpPerHour successful signups. Reserved names answer like taken ones.
Joining a team. /api/join/{slug} serves a team's join page at /join/{slug}: people become members of an existing team on its signup domain while the team has opened its signup (team_signup in its plan), or with one of its invitation links (?invite=). The team sets this up with /v1/tenants/{tenantId}/team-settings and /team-invites. The same limits apply. See docs/kb/team-admin.md.
General
/api/captcha
The captcha a public form needs
For purpose signup, join (team join pages), reset (password reset requests) or signin (webmail). An empty provider means none is needed. For pow, find a nonce such that SHA-256(salt ":" nonce) starts with difficulty zero bits and send "salt.expires.difficulty.signature.nonce" as captcha; each answer works once, for ten minutes. For turnstile and hcaptcha, render the provider's widget with siteKey and send its token. Signups whose address fails the platform's checks are held for approval or refused (403).
| Parameter | In | Type | |
|---|---|---|---|
purpose * | query | string |
200The challenge.{ "difficulty": 17, "expires": 1791460800, "provider": "pow", "salt": "Zq3...", "signature": "9b1c..." }400Unknown purpose.
/api/join/{slug}
What a team's join and sign-in pages show
404 for an unknown or suspended team, and for one with nothing to show (no title, message or colour, signup closed and no valid invitation). domain is set only when joining is possible. Webmail's sign-in page uses title, message and accent at /mail?team={slug}.
| Parameter | In | Type | |
|---|---|---|---|
slug * | path | string | |
invite | query | string |
200The team page.{ "accent": "#1a73e8", "domain": "acme.com", "inviteValid": true, "message": "Use your work name.", "name": "Acme", "signupOpen": false, "slug": "acme", "title": "Join Acme" }404No such team page.
/api/join/{slug}
Become a member of a team
Makes a member account localPart@ the team's signup domain, within its seats. Needs open signup or a valid invite, which is used up by one join. The team's welcome message (or the platform's) is sent.
| Parameter | In | Type | |
|---|---|---|---|
slug * | path | string |
{
"displayName": "Amy",
"invite": "tQ2x...",
"localPart": "amy",
"password": "correct horse battery"
}
201Joined.{ "accountId": "3d05579f-ed2b-40c4-a5a7-5a3717b216ec", "address": "amy@acme.com", "tenantId": "9b1c..." }403Signup is closed404No such team.409The address is taken or reserved422A value is not valid.429Too many signups from this network.
/api/signup
Create an account on a shared domain
201Created. The new tenant has one owner account.400Not a valid JSON object.403Signups are not open for that plan or domain, or the invitation is used, expired, cancelled or not accepted now.409The address is taken or reserved.415Not application/json.422A value is not valid.429Too many signups or attempts from this source address.
/api/signup/invitations/{code}
What an invitation link offers
For the signup page: the invitation's plan and the shared domains to choose from. Used, expired, cancelled and unknown links, and all links while the platform does not accept invitations, get the same 404.
| Parameter | In | Type | |
|---|---|---|---|
code * | path | string |
200The invitation can be used.404The invitation cannot be used.
/api/signup/options
What the signup page may offer
Plans and domains are empty unless at least one kind of signup is open.
200Open signups, plans and domains.