POST /auth/register
Create a new user in your project. This endpoint allows new users to sign up with email and password (password required).
Password sign-in: This flow requires a password. For passwordless (no password field), use OTP Send + OTP Verify instead.
Authentication: This endpoint does not require authentication. It’s a public endpoint.
Endpoint
POST /api/v1/public/projects/:projectSlug/auth/registerRequest Parameters
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
projectSlug | string | ✅ | Your project’s unique slug (found in admin dashboard) |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
email | string | ✅ | User’s email address (valid format) |
password | string | ✅ | Strong password (8+ chars, mixed case + number) |
name | string | ❌ | User’s display name |
customFields | object | Depends* | Keys must match Dashboard → Project → Auth → Custom Registration Fields. Prebuilt (e.g. phone) and custom keys go here. |
turnstileToken | string | ❌ | Turnstile token for captcha (if enabled) |
* Each custom field has its own required flag in admin. Required custom fields must be present or the API returns 400.
Prebuilt and Custom Fields
Prebuilt fields (system-recognized):
phone– Add a custom field with keyphonein admin to collect during signup. Send ascustomFields.phone. Stored in profile (returned by GET /auth/me). The dedicatedusers.phonecolumn is used only for Phone OTP signups.
Custom fields: Any other keys you configure (e.g. company, role). Stored in profile_extras.
Important: Only fields you add in Dashboard → Project → Auth → Custom Registration Fields are accepted. Unconfigured keys in customFields are ignored.
| Field | Required | Configured In |
|---|---|---|
| Yes | (always) | |
| password | Yes | (always) |
| name | No | (always) |
| customFields.* | Per field | Auth → Custom Registration Fields (key + required toggle) |
See Configuration scenarios for per-method examples (Email only, Phone only, Both).
Example Request Body
{
"email": "[email protected]",
"password": "StrongPassword123!",
"name": "Jane Doe",
"customFields": {
"company": "Acme Inc",
"phone": "+1-555-1234"
}
}Add phone and company in Auth → Custom Registration Fields first. Mark Required for any field that must be present.
Response
Success (200 OK)
When email verification is NOT required (or user already verified):
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": {
"id": "user-uuid-here",
"email": "[email protected]",
"name": "Jane Doe"
},
"requiresVerification": false
}When email verification IS required (project has “Require Email Verification” enabled):
{
"requiresVerification": true,
"message": "Please check your email to verify your account before signing in.",
"user": {
"id": "user-uuid-here",
"email": "[email protected]",
"name": "Jane Doe"
}
}No token is returned until the user verifies their email. Use GET /auth/verify-email (link in email) then POST /auth/login or POST /auth/otp/verify.
Error Responses
| Status Code | Error Code | Description |
|---|---|---|
| 400 | INVALID_REQUEST | Validation failed (weak password, invalid email, missing required fields) |
| 409 | EMAIL_ALREADY_IN_USE | An account with this email already exists |
| 429 | RATE_LIMIT_EXCEEDED | Too many signup requests from this IP |
| 500 | INTERNAL_SERVER_ERROR | Server error occurred |
Example Error Response
{
"error": "EMAIL_ALREADY_IN_USE",
"message": "An account with this email already exists",
"details": {
"email": "[email protected]"
}
}Available Hooks
This endpoint can trigger the following project-specific hooks:
Lifecycle Hooks
-
beforeSignup- When: After base validation, before user is created in database
- Can do: Enforce extra validation (e.g., only allow
@company.comemails), modify custom fields, throw errors to reject signup with custom message
-
afterSignup- When: After user is successfully created (and after verification email is queued if enabled)
- Can do: Send welcome emails, sync to CRM, create related records, enqueue background jobs
Webhook Events
- Event:
auth.signup- When: After successful user creation
- Can do: Call external webhooks (HTTP endpoints you host), run script hooks with the Hook SDK
Try It Now
https://api.aerocall.app/api/v1/public/projects/your-project/auth/registerSDK Examples
JavaScript / TypeScript
const response = await fetch(
'https://api.aerostack.dev/api/v1/public/projects/your-project/auth/register',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: '[email protected]',
password: 'StrongPassword123!',
name: 'Jane Doe'
})
}
);
if (!response.ok) {
const error = await response.json();
console.error('Signup failed:', error);
throw new Error(error.message);
}
const { user, token } = await response.json();
console.log('User created:', user);
// Store token for authenticated requests
localStorage.setItem('authToken', token);React
import { useState } from 'react';
function SignupForm() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [name, setName] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const handleSignup = async (e) => {
e.preventDefault();
setLoading(true);
setError(null);
try {
const res = await fetch('https://api.aerostack.dev/api/v1/public/projects/your-project/auth/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password, name })
});
if (!res.ok) {
const errorData = await res.json();
throw new Error(errorData.message);
}
const { user, token } = await res.json();
// Store token
localStorage.setItem('authToken', token);
// Redirect or update UI
window.location.href = '/dashboard';
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
return (
<form onSubmit={handleSignup} className="space-y-4">
{error && (
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded">
{error}
</div>
)}
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email"
required
className="w-full px-4 py-2 border rounded"
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Password"
required
minLength={8}
className="w-full px-4 py-2 border rounded"
/>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Name (optional)"
className="w-full px-4 py-2 border rounded"
/>
<button
type="submit"
disabled={loading}
className="w-full bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded disabled:opacity-50"
>
{loading ? 'Signing up...' : 'Sign Up'}
</button>
</form>
);
}React Native
import { useState } from 'react';
import { View, TextInput, Button, Alert, ActivityIndicator } from 'react-native';
import AsyncStorage from '@react-native-async-storage/async-storage';
export default function SignupScreen({ navigation }) {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [name, setName] = useState('');
const [loading, setLoading] = useState(false);
const handleSignup = async () => {
setLoading(true);
try {
const response = await fetch(
'https://api.aerostack.dev/api/v1/public/projects/your-project/auth/register',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ email, password, name })
}
);
const data = await response.json();
if (!response.ok) {
throw new Error(data.message || 'Signup failed');
}
const { user, token } = data;
// Store token securely
await AsyncStorage.setItem('authToken', token);
Alert.alert('Success', 'Account created successfully!');
navigation.navigate('Dashboard');
} catch (error) {
Alert.alert('Error', error.message);
} finally {
setLoading(false);
}
};
return (
<View style={{ padding: 20 }}>
<TextInput
placeholder="Email"
value={email}
onChangeText={setEmail}
autoCapitalize="none"
keyboardType="email-address"
style={{ borderWidth: 1, padding: 10, marginBottom: 10, borderRadius: 5 }}
/>
<TextInput
placeholder="Password"
value={password}
onChangeText={setPassword}
secureTextEntry
style={{ borderWidth: 1, padding: 10, marginBottom: 10, borderRadius: 5 }}
/>
<TextInput
placeholder="Name (optional)"
value={name}
onChangeText={setName}
style={{ borderWidth: 1, padding: 10, marginBottom: 15, borderRadius: 5 }}
/>
{loading ? (
<ActivityIndicator size="large" />
) : (
<Button title="Sign Up" onPress={handleSignup} />
)}
</View>
);
}Best Practices
Security: Always use HTTPS in production. Never expose API keys or sensitive tokens in client-side code.
✅ Validate input on both client and server side
✅ Use strong passwords - enforce minimum 8 characters with mixed case, numbers, and special characters
✅ Handle errors gracefully - show user-friendly error messages
✅ Store tokens securely - use httpOnly cookies for web or secure storage for mobile
✅ Implement rate limiting on your frontend to prevent abuse
✅ Consider email verification - enable in project settings for added security
Related Endpoints
- POST /auth/login - Authenticate existing users
- GET /auth/me - Get current user information
- POST /auth/resend-verification - Resend verification email
- POST /auth/reset-password-request - Request password reset
Need Help?
- 📖 Quick Start Guide: Get started in 5 minutes
- 💬 Discord: Join our community
- 📧 Support: [email protected]