Account
Manage the authenticated platform user — profile, password, email, and account deletion. All endpoints below require Authorization: Bearer TOKEN unless explicitly marked Public.
These endpoints manage your own Ghayma account (the developer using the platform). For managing the end-users of apps you’ve built on top of Auth Apps, see Auth Apps. For the public sign-in endpoints (/auth/register, /auth/login, OAuth), see the Auth Integration Guide.
Get Current User
GET /api/v1/auth/meReturns the authenticated user. When an email change is pending, the response also includes a pending_email_change field so the dashboard can render a confirmation banner.
Response: 200
{
"id": "u_abc123",
"email": "[email protected]",
"name": "Alice",
"email_verified": true,
"avatar_url": "https://...",
"auth_provider": "email",
"password_updated_at": "2026-04-12T10:14:22Z",
"role": "user",
"created_at": "2026-01-03T08:00:00Z",
"pending_email_change": {
"new_email": "[email protected]",
"expires_at": "2026-05-13T16:00:00Z"
}
}pending_email_change is omitted when there is no active request — check for its presence rather than expecting a null. auth_provider is "email", "google", or "github".
Update Profile
PATCH /api/v1/auth/meEdits the caller’s own profile. Only name is mutable today; avatar_url is OAuth-sourced and read-only here.
Body:
{
"name": "Alice Cooper"
}| Field | Type | Constraints |
|---|---|---|
name | string | Max 100 characters; trimmed |
Returns the updated user object (same shape as GET /auth/me, without pending_email_change).
Change Password
POST /api/v1/auth/change-passwordRotates the password for an email-provider account. Rate-limited to 5 requests per minute per IP. On success, a security notice is sent to the current email address and password_updated_at is stamped.
Body:
{
"current_password": "oldsecret123",
"new_password": "newsecret456"
}| Field | Type | Constraints |
|---|---|---|
current_password | string | Required |
new_password | string | Required, minimum 8 characters |
Response: 200
{ "message": "password updated" }Errors
| Status | error | When |
|---|---|---|
400 | password must be at least 8 characters | New password too short |
400 | new password must differ from current | New password equals current |
401 | current password incorrect | current_password does not match |
OAuth-only accounts (Google, GitHub) do not have a local password and cannot use this endpoint.
Email Change
Changing email is a three-step, double-confirmed flow designed to make silent account takeover infeasible:
- The user submits the new address + current password → confirmation link is emailed to the new address; a heads-up notice is emailed to the old address.
- The user clicks the link (on any device, signed in or not) → the change is applied atomically.
- A second notice is sent to the old address after the swap. The pending request expires after 1 hour if not confirmed.
OAuth-linked accounts (auth_provider ≠ "email") cannot use this flow — their canonical email is pinned to the upstream provider and must be changed there.
Request Email Change
POST /api/v1/auth/email/change-requestInitiates a pending change. Rate-limited to 3 requests per hour per IP. Any prior pending request for the same user is marked consumed before the new one is issued, so there is always at most one live token per account.
Body:
{
"new_email": "[email protected]",
"current_password": "secret123"
}Response: 200
{
"message": "Check your new email to confirm the change",
"expires_at": "2026-05-13T16:00:00Z"
}Errors
| Status | error | When |
|---|---|---|
400 | new email must differ from current | Same as the address already on file |
400 | invalid email format | Doesn’t contain @ |
400 | this account uses google sign-in; change your email there first | OAuth-linked account (provider name varies) |
400 | could not start email change | Target is already taken by another account (generic by design — does not leak account existence) |
401 | current password incorrect | current_password does not match |
Confirm Email Change
POST /api/v1/auth/email/change-confirmPublic — no Authorization header required. The token from the confirmation email is the credential. This lets users confirm from a different device that isn’t signed in.
Body:
{
"token": "a1b2c3..."
}Response: 200
{
"message": "email updated",
"email": "[email protected]"
}On success, the user row is updated atomically inside a transaction: email is swapped, email_verified is set to true (the click is proof of ownership), and the legacy verification_token is cleared. A final notice is sent to the old address.
Errors
| Status | error | When |
|---|---|---|
400 | invalid or expired token | Token unknown, already consumed, or past its 1-hour TTL |
409 | that email is now taken; please request again with a different address | Someone else registered the target address between request and confirm |
Cancel Email Change
DELETE /api/v1/auth/email/change-requestMarks the caller’s pending email change as consumed. Idempotent — succeeds even if there is no pending request. The row is preserved (not hard-deleted) so the audit trail stays intact.
Response: 200
{ "message": "pending email change cancelled" }Delete Account
DELETE /api/v1/auth/accountTerminal. Removes the user. Returns 409 if the account still has resources that block deletion (e.g. active projects, unpaid balance).
Credit left on the billing accounts you own is forfeited — it is never paid out. The first call refuses with 409 and code: balance_forfeit_required, listing the amounts in balances and total_milli_dzd; repeat it with a body to confirm:
{ "forfeit_balance": true }A negative balance is never forfeited: it answers 409 with code: balance_negative until it is settled with support. See Billing, Cycles & Credits.
Response: 200
{ "message": "account deleted" }Security Notifications
All notifications are sent best-effort over SMTP and never block the underlying request. The transactional templates set RFC 8058 one-click unsubscribe headers but are not marked Precedence: bulk.
| Trigger | Sent to | Subject |
|---|---|---|
| Email change requested | New address | ”Confirm your new Ghayma email” |
| Email change requested | Old address | ”Email change requested on your Ghayma account” |
| Email change confirmed | Old address | ”Your Ghayma email was changed” |
| Password changed | Current address | ”Your Ghayma password was changed” |
The notices to the old address exist so that an attacker who somehow obtained a valid session and rotated the email or password can’t move silently — the legitimate user still gets one last signal at the address they recognise.