Auth
Rotate your own password. Requires the CURRENT password: a session alone is not sufficient, which is what stops a stolen token from being upgraded into a stolen credential. On success EVERY session is revoked -- including this one -- and outstanding access tokens are invalidated immediately, so the caller must log in again with the new password.
BearerAuthAuthorizationBearer <token>JWT access token. Obtain via POST /v1/auth/login.
application/json- body
Authenticated password change. Identity comes from the SESSION, not the body.
There is deliberately no email field. The user is already authenticated,
so accepting one would let a caller nominate an account other than their
own — the endpoint would be an authorization bug wearing a form.
current_password*Current PasswordThe password being replaced. Verified before anything is written — this is what stops a stolen session token from being upgraded into a stolen credential.
1 <= length <= 128new_password*New PasswordNew password (12-128 chars). Must use 3 of 4 character classes (lowercase, uppercase, digit, special) or be 16+ characters.
12 <= length <= 128Successful Response
application/json- response
message?MessageConfirmation that the password changed and every refresh token was revoked. The caller's OWN session is revoked too — they cannot distinguish it from an attacker's.
"Password changed. All sessions were signed out, including this one; please log in with your new password."curl -X POST 'https://api.aisc.sa/v1/auth/change-password' \ -H 'Content-Type: application/json' \ -d '{ "current_password": "string", "new_password": "MyNewSecureP@ss123"}'{ "message": "Password changed. All sessions were signed out, including this one; please log in with your new password."}Log in with email and password. On success, returns access token (JWT, typically 1h TTL) and sets httpOnly refresh cookie (14 days, SameSite=Lax). Use access token in Authorization: Bearer header for subsequent requests. Refresh token binds to the issued access token (H-24 binding prevents token mixups).
BearerAuthAuthorizationBearer <token>JWT access token. Obtain via POST /v1/auth/login.
application/json- body
email*EmailRegistered email address.
emailpassword*PasswordAccount password.
1 <= length <= 128Successful Response
application/json- response
Returned by login, verify-email, refresh — the full session envelope.
access_token*Access TokenJWT access token. Valid for ~1 hour. Include in Authorization: Bearer header.
email*EmailAuthenticated user's email address.
expires_in*Expires InAccess token TTL in seconds from issue time.
telemetry_consent?|The user's analytics consent, on login (spec C REQ-OBS-35); null on the other routes and when it could not be read.
tier*TierSubscription tier.
token_type?Token TypeToken type (always 'Bearer' for JWT).
"Bearer"user_id*User IdAuthenticated user's UUID.
uuidcurl -X POST 'https://api.aisc.sa/v1/auth/login' \ -H 'Content-Type: application/json' \ -d '{ "email": "user@example.com", "password": "MySecureP@ss123"}'{ "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "email": "user@example.com", "expires_in": 3600, "telemetry_consent": { "effective_from": "2019-08-24T14:15:22Z", "notice_version": "v1", "state": "unset" }, "tier": "free", "token_type": "Bearer", "user_id": "550e8400-e29b-41d4-a716-446655440000"}Invalidate the current session. The refresh token is revoked in Redis and the cookie is deleted. Subsequent API calls without a fresh access token will fail with 401. The access token itself remains valid until exp, but is typically discarded client-side.
BearerAuthAuthorizationBearer <token>JWT access token. Obtain via POST /v1/auth/login.
Successful Response
curl -X POST 'https://api.aisc.sa/v1/auth/logout'Return the authenticated user's profile (email, tier, language preference, status). Requires valid Authorization: Bearer access_token header.
BearerAuthAuthorizationBearer <token>JWT access token. Obtain via POST /v1/auth/login.
Successful Response
application/json- response
GET /v1/auth/me — the current user's profile.
account_type?string|nulldisplay_name?string|nullemail*Emaileula_version_accepted?|The EULA version the user last accepted (spec E REQ-30); null on PATCH, when the user has accepted none, and when it could not be read.
has_verified_email*Has Verified EmailWhether email is verified
is_org_bound*Is Org BoundCreated through an org invite (organization-only view)
phone?string|nullpreferred_lang*Preferred LangISO 639-1 language code
sector?string|nullstatus*StatusAccount status
subscription_tier*Subscription TierCurrent subscription tier
user_id*User Iduuidcurl -X GET 'https://api.aisc.sa/v1/auth/me'{ "account_type": "individual", "display_name": "Yousef A.", "email": "user@example.com", "eula_version_accepted": "2.0-2026-10-11", "has_verified_email": true, "is_org_bound": false, "phone": "+966501234567", "preferred_lang": "en", "sector": "architecture", "status": "active", "subscription_tier": "free", "user_id": "550e8400-e29b-41d4-a716-446655440000"}Partially update the authenticated user's profile. Only fields present in the request body are changed (display_name, phone, sector, preferred_lang); an explicit null clears a nullable field. Email, password and account_type are not updatable here. Returns the updated profile.
BearerAuthAuthorizationBearer <token>JWT access token. Obtain via POST /v1/auth/login.
application/json- body
PATCH /v1/auth/me — partial profile update (F-022).
Only fields present in the request are updated; explicit null clears a nullable field. Email / password / account_type are NOT updatable here.
display_name?|nullphone?|nullpreferred_lang?|nullsector?|nullSuccessful Response
application/json- response
GET /v1/auth/me — the current user's profile.
account_type?string|nulldisplay_name?string|nullemail*Emaileula_version_accepted?|The EULA version the user last accepted (spec E REQ-30); null on PATCH, when the user has accepted none, and when it could not be read.
has_verified_email*Has Verified EmailWhether email is verified
is_org_bound*Is Org BoundCreated through an org invite (organization-only view)
phone?string|nullpreferred_lang*Preferred LangISO 639-1 language code
sector?string|nullstatus*StatusAccount status
subscription_tier*Subscription TierCurrent subscription tier
user_id*User Iduuidcurl -X PATCH 'https://api.aisc.sa/v1/auth/me' \ -H 'Content-Type: application/json' \ -d '{}'{ "account_type": "individual", "display_name": "Yousef A.", "email": "user@example.com", "eula_version_accepted": "2.0-2026-10-11", "has_verified_email": true, "is_org_bound": false, "phone": "+966501234567", "preferred_lang": "en", "sector": "architecture", "status": "active", "subscription_tier": "free", "user_id": "550e8400-e29b-41d4-a716-446655440000"}Exchange the refresh cookie + current access token for a new access token pair. The refresh token is rotated on each call (H-24: binding to access_token.jti prevents reuse). Requires both the refresh cookie (httpOnly, sent automatically by the browser) and Authorization: Bearer header with the current access token (even if expired by exp claim). On refresh failure, the refresh cookie is cleared (user bounced to login).
BearerAuthAuthorizationBearer <token>JWT access token. Obtain via POST /v1/auth/login.
Successful Response
application/json- response
Returned by login, verify-email, refresh — the full session envelope.
access_token*Access TokenJWT access token. Valid for ~1 hour. Include in Authorization: Bearer header.
email*EmailAuthenticated user's email address.
expires_in*Expires InAccess token TTL in seconds from issue time.
telemetry_consent?|The user's analytics consent, on login (spec C REQ-OBS-35); null on the other routes and when it could not be read.
tier*TierSubscription tier.
token_type?Token TypeToken type (always 'Bearer' for JWT).
"Bearer"user_id*User IdAuthenticated user's UUID.
uuidcurl -X POST 'https://api.aisc.sa/v1/auth/refresh'{ "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "email": "user@example.com", "expires_in": 3600, "telemetry_consent": { "effective_from": "2019-08-24T14:15:22Z", "notice_version": "v1", "state": "unset" }, "tier": "free", "token_type": "Bearer", "user_id": "550e8400-e29b-41d4-a716-446655440000"}Register a new user with email and password. A verification OTP is sent to the email; call POST /verify-email with the OTP to activate. Password must be 12+ characters, with complexity enforced (3 of 4 character classes or 16+ character passphrase).
BearerAuthAuthorizationBearer <token>JWT access token. Obtain via POST /v1/auth/login.
application/json- body
account_type?|Optional account type ('individual' or 'organization').
display_name?|Optional display name.
email*EmailEmail address for the new account. Must be unique.
emaileula_lang?|Language of the EULA text the user accepted (spec E REQ-30).
eula_sha256*Eula Sha256SHA-256 (64 hex chars) of the exact EULA document the user accepted.
^[a-fA-F0-9]{64}$eula_version*Eula VersionVersion identifier of the EULA the user accepted.
length <= 20password*PasswordPassword (12-128 chars). Must use 3 of 4 character classes (lowercase, uppercase, digit, special) or be 16+ characters long.
12 <= length <= 128phone?|Optional phone number.
preferred_lang?Preferred LangPreferred language. Must be 'ar' or 'en'. Defaults to 'en'. Used for error messages and email templates.
"en""ar""en"sector?|Optional industry sector.
telemetry_consent?|The analytics choice made on the consent banner before signing up, if any.
Successful Response
application/json- response
message?MessageUser-facing message indicating next step (email verification).
"Verification email sent. Check your inbox."user_id*User IdUUID of the new user account.
uuidcurl -X POST 'https://api.aisc.sa/v1/auth/register' \ -H 'Content-Type: application/json' \ -d '{ "email": "user@example.com", "eula_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "eula_version": "2026-06-01", "password": "MySecureP@ss123"}'{ "message": "Verification email sent. Check your inbox.", "user_id": "550e8400-e29b-41d4-a716-446655440000"}Send a 6-digit password-reset code to the given email. The response is DELIBERATELY identical whether or not the email is registered — this endpoint cannot be used to discover which addresses have accounts. Call POST /reset-password with the code to complete.
BearerAuthAuthorizationBearer <token>JWT access token. Obtain via POST /v1/auth/login.
application/json- body
email*EmailEmail address of the account to reset. The response is deliberately identical whether or not this email is registered (no account-existence disclosure).
emailSuccessful Response
application/json- response
message?MessageGeneric, non-enumerable confirmation. Always returned regardless of whether the email exists.
"If that email is registered, a reset code has been sent."curl -X POST 'https://api.aisc.sa/v1/auth/request-password-reset' \ -H 'Content-Type: application/json' \ -d '{ "email": "user@example.com"}'{ "message": "If that email is registered, a reset code has been sent."}Re-send the 6-digit signup OTP to the given email. The response is DELIBERATELY identical whether or not the email has a pending registration — this endpoint cannot be used to discover which addresses have accounts. Only accounts still awaiting email verification receive a new code.
BearerAuthAuthorizationBearer <token>JWT access token. Obtain via POST /v1/auth/login.
application/json- body
email*EmailEmail address to re-send the signup OTP to. The response is deliberately identical whether or not this email is registered / pending (no account-existence disclosure).
emailSuccessful Response
application/json- response
message?MessageGeneric, non-enumerable confirmation. Always returned.
"If that email has a pending registration, a new code has been sent."curl -X POST 'https://api.aisc.sa/v1/auth/resend-verification' \ -H 'Content-Type: application/json' \ -d '{ "email": "user@example.com"}'{ "message": "If that email has a pending registration, a new code has been sent."}Set a new password using the 6-digit code from POST /request-password-reset. On success the password is changed and ALL existing sessions are revoked (the user must log in again). Errors are generic (no account-existence disclosure).
BearerAuthAuthorizationBearer <token>JWT access token. Obtain via POST /v1/auth/login.
application/json- body
email*EmailEmail address the reset code was requested for.
emailnew_password*New PasswordNew password (12-128 chars). Must use 3 of 4 character classes (lowercase, uppercase, digit, special) or be 16+ characters.
12 <= length <= 128otp*Otp6-digit reset code sent to the email.
^\d{6}$6 <= length <= 6Successful Response
application/json- response
message?MessageConfirmation that the password changed and all refresh tokens were revoked. The user must log in again.
"Password reset. All existing sessions were signed out; please log in with your new password."curl -X POST 'https://api.aisc.sa/v1/auth/reset-password' \ -H 'Content-Type: application/json' \ -d '{ "email": "user@example.com", "new_password": "MyNewSecureP@ss123", "otp": "123456"}'{ "message": "Password reset. All existing sessions were signed out; please log in with your new password."}Complete email verification by submitting the 6-digit OTP sent to the registered email. On success, returns access token and sets httpOnly refresh cookie. The refresh token is bound to the access token (H-24 binding).
BearerAuthAuthorizationBearer <token>JWT access token. Obtain via POST /v1/auth/login.
application/json- body
F-022: keyed by EITHER user_id (legacy) OR email — exactly one.
email?|Registered email address. Supply exactly one of user_id / email.
marketing_email?Marketing EmailThe unticked marketing-email box was ticked: consent to news and offers by email, recorded with the verification under the API's own wording version.
falsenew_password*New PasswordThe account's password, chosen by whoever holds the inbox. Replaces any password given at registration. Same rules as registration.
12 <= length <= 128otp*Otp6-digit OTP sent to the registered email address.
^\d{6}$6 <= length <= 6user_id?|UUID from the register response. Supply exactly one of user_id / email.
Successful Response
application/json- response
Returned by login, verify-email, refresh — the full session envelope.
access_token*Access TokenJWT access token. Valid for ~1 hour. Include in Authorization: Bearer header.
email*EmailAuthenticated user's email address.
expires_in*Expires InAccess token TTL in seconds from issue time.
telemetry_consent?|The user's analytics consent, on login (spec C REQ-OBS-35); null on the other routes and when it could not be read.
tier*TierSubscription tier.
token_type?Token TypeToken type (always 'Bearer' for JWT).
"Bearer"user_id*User IdAuthenticated user's UUID.
uuidcurl -X POST 'https://api.aisc.sa/v1/auth/verify-email' \ -H 'Content-Type: application/json' \ -d '{ "new_password": "MySecureP@ss123", "otp": "123456"}'{ "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "email": "user@example.com", "expires_in": 3600, "telemetry_consent": { "effective_from": "2019-08-24T14:15:22Z", "notice_version": "v1", "state": "unset" }, "tier": "free", "token_type": "Bearer", "user_id": "550e8400-e29b-41d4-a716-446655440000"}