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.
Through Zappocity. While single sign-on is on, accounts are made with a Zappocity account: GET /api/signup/identity says so (required), the page sends the browser to startUrl to sign in to zappo.city (or create the account there, with a confirmed email), and POST /api/signup and POST /api/join/{slug} then take no password: the mailbox has none of its own and is linked to that Zappocity account, which signs in to it. Without that sign-in (kept 30 minutes, for one account) they answer 403 with zappocityRequired and startUrl. A signup through Zappocity needs no verification code or captcha: the confirmed Zappocity account stands for them.
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 closed, the invitation is not valid, or, while single sign-on is on, no Zappocity sign-in (zappocityRequired, startUrl).404No 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; the invitation is used, expired, cancelled or not accepted now; or, while single sign-on is on, no Zappocity sign-in (zappocityRequired, startUrl).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/code
Send a signup verification code
A 6-digit code, valid 15 minutes, by email to an address at another provider (not a domain hosted here) or by text message to a mobile number with its country code. Asks for the signup captcha. At most 5 codes an hour from one network and 3 to one address or number; a number verifies at most phoneLimit accounts in 30 days.
{
"captcha": "...",
"method": "sms",
"to": "+1 415 555 0100"
}
201Sent; pass id with the code to POST /api/signup.{ "id": "6f1c2e8a-3b5d-4c7e-9f10-2a3b4c5d6e7f", "method": "sms", "to": "+14155550100" }403The captcha is missing or wrong, or the number has verified as many accounts as it may.422Not offered this way, or not a valid address or number (or an address hosted here).429Too many codes for now.503The code could not be sent.
/api/signup/identity
Whether accounts are made through Zappocity, and as whom
| Parameter | In | Type | |
|---|---|---|---|
next | query | string | The page to come back to (/signup or /join/{slug}, with its query). |
200required is true while single sign-on is on; then startUrl signs in to Zappocity and comes back to next, and email and name are there once this browser has.{ "email": "amy@example.com", "name": "Amy", "required": true, "startUrl": "/api/signup/sso/start?next=%2Fsignup" }
/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.
/api/signup/sso/callback
Where Zappocity sends the browser back
| Parameter | In | Type | |
|---|---|---|---|
code | query | string | |
state | query | string |
302To next, signed in for a signup for 30 minutes; or to next#sso-error=... (expired, unverified, unavailable).
/api/signup/sso/start
Sign in to Zappocity (or create the account there) for a signup
| Parameter | In | Type | |
|---|---|---|---|
next | query | string |
302To Zappocity; or straight to next while single sign-on is off.