---
title: "Sign up with email + password"
method: POST
path: "/auth/sign-up"
tags: ["Auth"]
---

# Sign up with email + password

`POST /auth/sign-up`

## Request

- Base URL: `https://api.mnemom.ai/v1`
- URL: `https://api.mnemom.ai/v1/auth/sign-up`
- Auth: none required

Creates a new user account. When email confirmation is required on the Supabase project, returns `{ user, email_confirmation_required: true }` without issuing a session cookie; the user must click the email link. When auto-confirm is on, issues `mnemom_session` and returns `{ user }` — except for an email domain that enforces SSO, which gets 403 `sso_required` (503 `sso_check_unavailable` if the SSO lookup fails) and no session; that user signs in through SSO. If the email already has an account, the response is identical to a new confirmation-required signup (`200 { user: null, email_confirmation_required: true }`) so the endpoint cannot be used to enumerate registered emails (CWE-203); no duplicate account is created, and the existing owner is emailed sign-in / reset links out-of-band. Supersedes the earlier `409 email_exists` behavior (MNE-1348). Invite variant (MNE-8331): a valid `invite_token` that vouches for `email` skips email confirmation and returns a live session (see `invite_token`).

## Request body

- object
  - `email` string, email, required
  - `password` string, password, required
  - `redirect_to` string, uri: Allowlisted SPA path to return to after email confirmation.
  - `invite_token` string: Optional product-invite token (`pinv_…`) from an invite link. When it resolves to a PENDING, email-bound invite whose own invitee email equals `email` exactly (trimmed, case-insensitive), the account is created with its email already verified and a session is issued (`{ user }`, no `email_confirmation_required`) — the invite email already proved inbox control. Applies on the Zitadel identity provider; on a GoTrue-backed deployment the token is ignored and the normal confirmation-required answer is returned. The invite is NOT accepted by this call (use `POST /invites/accept`). Any other value (unknown, used, expired, revoked, user-bound, or a different email) is silently ignored and the response is identical to a sign-up without the token; no response reveals whether a token exists or whether the email matched (MNE-1348).

## Response `200`

Account created. Either returns a full session (auto-confirm) or awaits email confirmation.

- object
  - `user` User: Supabase user shape. Fields beyond `id` and `email` are passthrough.
    - `id` string, uuid
    - `email` string, email
    - `app_metadata` object
    - `user_metadata` object
  - `email_confirmation_required` true, required

## Other responses

- `400`: Bad request
- `403`: Forbidden
- `409`: Resource conflict — the request cannot proceed because of a current state conflict (e.g., resource already exists, version mismatch, in-flight idempotency key with a different body).
- `415`: Body is not declared `application/json` (`unsupported_media_type`). Login-CSRF guard: a cross-site `<form enctype="text/plain">` cannot deliver a parseable body.
- `422`: Request was well-formed but semantically invalid (e.g., business-rule validation failure, malformed YAML inside a JSON payload).
- `429`: Rate-limit exceeded. The global per-IP limiter (100 requests/minute, applied to every `/v1/*` route) rejected this request. Back off until the window resets — `Retry-After` carries the cooldown in seconds and `X-RateLimit-Reset` the absolute reset time.
- `500`: Server error — request was well-formed but the server failed to fulfill it. Typically a downstream dependency (DB / RPC / external API) returned an unexpected error.
- `503`: An upstream dependency is currently unavailable. The request was not processed; retry after the cooldown window.

---

[API](https://skmtc.dev/mnemom/apis/mnemom-api.md) · [All operations](https://skmtc.dev/mnemom/apis/mnemom-api/llms.txt) · [OpenAPI document](https://skmtc.dev/mnemom/apis/mnemom-api/revisions/274b7a6ea182?raw)
