Skip to main content

POST /v1/auth/register

Create a new organization and its owner user in a single atomic operation. Returns a fully-issued browser session on success.

::: tip Auth Required: none. This endpoint is intentionally unauthenticated — it's the on-ramp for new tenants. :::

Request

POST /v1/auth/register

HeaderRequiredNotes
Content-Type: application/jsonyes

Body

{
"email": "alice@example.com",
"password": "correct-horse-battery-staple",
"display_name": "Alice Anderson",
"organization_name": "Acme Co.",
"organization_slug": "acme-co"
}
FieldTypeRequiredNotes
emailstringyesValid email format. Must be globally unique.
passwordstringyesMinimum 12 characters, max 256.
display_namestring | nullnoShown in the UI; defaults to email if not set.
organization_namestringyes1–100 chars. Human-readable name.
organization_slugstring | nullnoURL-safe identifier. If omitted, derived from the name. Must match [a-z0-9-]{1,100}.

Response

201 Created

A full LoginResponse::Success body, plus session cookies on the response:

{
"status": "success",
"user": {
"user_id": "1c8b2a5e-...",
"organization_id": "ec3f7b1a-...",
"email": "alice@example.com",
"is_platform_admin": false,
"platform_role": null,
"api_key_id": null,
"api_key_prefix": null,
"api_key_name": null,
"team_id": null,
"project_id": null,
"auth_method": "password",
"session_family_id": "9a4d3c1e-...",
"mfa_verified_at": null,
"scopes": [],
"allowed_ips": [],
"allowed_referrers": [],
"extensions": {}
},
"capabilities": {
"role": "owner",
"permissions": ["org.read", "members.invite", "members.remove", "..."]
},
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"expires_in": 900
}

Cookies set:

  • identsphere_at — access token, HttpOnly, scope /
  • identsphere_rt — refresh token, HttpOnly, scope /v1/auth
  • identsphere_csrf — CSRF token, scope /, NOT HttpOnly

Error responses

StatusCodeWhen
400invalid_inputEmail format invalid, password too short, slug invalid format.
409conflictemail already registered or organization slug already taken.
500internal_errorDatabase, hashing, or JWT failure.

Example: curl

curl -X POST https://auth.example.com/v1/auth/register \
-H 'Content-Type: application/json' \
-d '{
"email": "alice@example.com",
"password": "correct-horse-battery-staple",
"organization_name": "Acme Co."
}'

Example: TypeScript (@identsphere/react)

import { useRegister } from '@identsphere/react';

function SignupForm() {
const register = useRegister();
return (
<button
onClick={() =>
register.mutateAsync({
email: 'alice@example.com',
password: 'correct-horse-battery-staple',
organization_name: 'Acme Co.',
})
}
>
Sign up
</button>
);
}

Notes

  • This endpoint always creates a new organization. To invite an existing user into an existing organization, use POST /v1/orgs/:org_id/invitations.
  • The new user is assigned the owner role on the new org.
  • A registration audit entry (auth.register) is recorded asynchronously.
  • The response is identical in shape to a successful POST /v1/auth/login — client code can share a single handler.