curl --request POST \
--url https://pria.praxislxp.com/api/auth/apple \
--header 'Content-Type: application/json' \
--data '
{
"identityToken": "eyJraWQiOiJXNldjT0tCIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwic3ViIjoiMDAxMjM0LmFiY2RlZi4xMjM0IiwiYXVkIjoiY29tLnByYXhpcy5wcmlhIiwiZXhwIjoxNzU3NTAwMDAwLCJpYXQiOjE3NTc0OTk0MDAsIm5vbmNlIjoiYWJjMTIzIiwiZW1haWwiOiJqYW5lLmRvZUBwcml2YXRlcmVsYXkuYXBwbGVpZC5jb20iLCJlbWFpbF92ZXJpZmllZCI6InRydWUiLCJpc19wcml2YXRlX2VtYWlsIjoidHJ1ZSJ9.signature",
"nonce": "f3a1c9e2b6d4",
"fullName": {
"givenName": "Ada",
"familyName": "Lovelace"
},
"brandingId": "pub_6430736fd62d650040420674"
}
'import requests
url = "https://pria.praxislxp.com/api/auth/apple"
payload = {
"identityToken": "eyJraWQiOiJXNldjT0tCIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwic3ViIjoiMDAxMjM0LmFiY2RlZi4xMjM0IiwiYXVkIjoiY29tLnByYXhpcy5wcmlhIiwiZXhwIjoxNzU3NTAwMDAwLCJpYXQiOjE3NTc0OTk0MDAsIm5vbmNlIjoiYWJjMTIzIiwiZW1haWwiOiJqYW5lLmRvZUBwcml2YXRlcmVsYXkuYXBwbGVpZC5jb20iLCJlbWFpbF92ZXJpZmllZCI6InRydWUiLCJpc19wcml2YXRlX2VtYWlsIjoidHJ1ZSJ9.signature",
"nonce": "f3a1c9e2b6d4",
"fullName": {
"givenName": "Ada",
"familyName": "Lovelace"
},
"brandingId": "pub_6430736fd62d650040420674"
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
identityToken: 'eyJraWQiOiJXNldjT0tCIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwic3ViIjoiMDAxMjM0LmFiY2RlZi4xMjM0IiwiYXVkIjoiY29tLnByYXhpcy5wcmlhIiwiZXhwIjoxNzU3NTAwMDAwLCJpYXQiOjE3NTc0OTk0MDAsIm5vbmNlIjoiYWJjMTIzIiwiZW1haWwiOiJqYW5lLmRvZUBwcml2YXRlcmVsYXkuYXBwbGVpZC5jb20iLCJlbWFpbF92ZXJpZmllZCI6InRydWUiLCJpc19wcml2YXRlX2VtYWlsIjoidHJ1ZSJ9.signature',
nonce: 'f3a1c9e2b6d4',
fullName: {givenName: 'Ada', familyName: 'Lovelace'},
brandingId: 'pub_6430736fd62d650040420674'
})
};
fetch('https://pria.praxislxp.com/api/auth/apple', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://pria.praxislxp.com/api/auth/apple",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'identityToken' => 'eyJraWQiOiJXNldjT0tCIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwic3ViIjoiMDAxMjM0LmFiY2RlZi4xMjM0IiwiYXVkIjoiY29tLnByYXhpcy5wcmlhIiwiZXhwIjoxNzU3NTAwMDAwLCJpYXQiOjE3NTc0OTk0MDAsIm5vbmNlIjoiYWJjMTIzIiwiZW1haWwiOiJqYW5lLmRvZUBwcml2YXRlcmVsYXkuYXBwbGVpZC5jb20iLCJlbWFpbF92ZXJpZmllZCI6InRydWUiLCJpc19wcml2YXRlX2VtYWlsIjoidHJ1ZSJ9.signature',
'nonce' => 'f3a1c9e2b6d4',
'fullName' => [
'givenName' => 'Ada',
'familyName' => 'Lovelace'
],
'brandingId' => 'pub_6430736fd62d650040420674'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://pria.praxislxp.com/api/auth/apple"
payload := strings.NewReader("{\n \"identityToken\": \"eyJraWQiOiJXNldjT0tCIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwic3ViIjoiMDAxMjM0LmFiY2RlZi4xMjM0IiwiYXVkIjoiY29tLnByYXhpcy5wcmlhIiwiZXhwIjoxNzU3NTAwMDAwLCJpYXQiOjE3NTc0OTk0MDAsIm5vbmNlIjoiYWJjMTIzIiwiZW1haWwiOiJqYW5lLmRvZUBwcml2YXRlcmVsYXkuYXBwbGVpZC5jb20iLCJlbWFpbF92ZXJpZmllZCI6InRydWUiLCJpc19wcml2YXRlX2VtYWlsIjoidHJ1ZSJ9.signature\",\n \"nonce\": \"f3a1c9e2b6d4\",\n \"fullName\": {\n \"givenName\": \"Ada\",\n \"familyName\": \"Lovelace\"\n },\n \"brandingId\": \"pub_6430736fd62d650040420674\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://pria.praxislxp.com/api/auth/apple")
.header("Content-Type", "application/json")
.body("{\n \"identityToken\": \"eyJraWQiOiJXNldjT0tCIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwic3ViIjoiMDAxMjM0LmFiY2RlZi4xMjM0IiwiYXVkIjoiY29tLnByYXhpcy5wcmlhIiwiZXhwIjoxNzU3NTAwMDAwLCJpYXQiOjE3NTc0OTk0MDAsIm5vbmNlIjoiYWJjMTIzIiwiZW1haWwiOiJqYW5lLmRvZUBwcml2YXRlcmVsYXkuYXBwbGVpZC5jb20iLCJlbWFpbF92ZXJpZmllZCI6InRydWUiLCJpc19wcml2YXRlX2VtYWlsIjoidHJ1ZSJ9.signature\",\n \"nonce\": \"f3a1c9e2b6d4\",\n \"fullName\": {\n \"givenName\": \"Ada\",\n \"familyName\": \"Lovelace\"\n },\n \"brandingId\": \"pub_6430736fd62d650040420674\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://pria.praxislxp.com/api/auth/apple")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"identityToken\": \"eyJraWQiOiJXNldjT0tCIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwic3ViIjoiMDAxMjM0LmFiY2RlZi4xMjM0IiwiYXVkIjoiY29tLnByYXhpcy5wcmlhIiwiZXhwIjoxNzU3NTAwMDAwLCJpYXQiOjE3NTc0OTk0MDAsIm5vbmNlIjoiYWJjMTIzIiwiZW1haWwiOiJqYW5lLmRvZUBwcml2YXRlcmVsYXkuYXBwbGVpZC5jb20iLCJlbWFpbF92ZXJpZmllZCI6InRydWUiLCJpc19wcml2YXRlX2VtYWlsIjoidHJ1ZSJ9.signature\",\n \"nonce\": \"f3a1c9e2b6d4\",\n \"fullName\": {\n \"givenName\": \"Ada\",\n \"familyName\": \"Lovelace\"\n },\n \"brandingId\": \"pub_6430736fd62d650040420674\"\n}"
response = http.request(request)
puts response.read_body{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2NDMwNzM2ZmQ2MmQ2NTAwNDA0MjA2NzQiLCJlbWFpbCI6ImpvaG4uZG9lQG15ZG9tYWluLmNvbSIsImN1c3RvbWVySWQiOiJjdXNfTnh4eHh4eCIsImFjY291bnRUeXBlIjoidXNlciIsInNlc3Npb25JZCI6InMlM0FhYmMxMjMiLCJpYXQiOjE3MDAwMDAwMDAsImV4cCI6MTcwMDA4NjQwMH0.signature",
"profile": {
"_id": "<string>",
"email": "jsmith@example.com",
"fname": "<string>",
"lname": "<string>",
"picture": "<string>",
"accountType": "<string>",
"permissions": [
"<string>"
],
"customerId": "<string>",
"lxp_user_id": "<string>",
"lxp_user_type": 123,
"lxp_partner_id": "<string>",
"lxp_partner_name": "<string>",
"lxp_role_id": 123,
"lxp_role_name": "<string>",
"credits": 123,
"creditsUsed": 123,
"plan": "<string>",
"status": "<string>",
"trial_end": "2023-11-07T05:31:56Z",
"trial_used": true,
"current_period_end": "2023-11-07T05:31:56Z",
"cancel_at_period_end": true,
"referralId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"referrerPaid": true,
"resetCodeId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"invoices_urls": [
"<string>"
],
"remember_history_count": 123,
"browser_voice": "<string>",
"rt_voice": "<string>",
"use_location": true,
"showSideBar": true,
"dark_mode": true,
"created": "2023-11-07T05:31:56Z",
"__v": 123,
"institution": {
"_id": "<string>",
"name": "<string>",
"picture": "<string>",
"picture_bg": "<string>",
"picture_dark_bg": "<string>",
"picture_animated": "<string>",
"elevenlabs_agent_id": "<string>",
"credits": 123,
"status": "<string>",
"allowJoining": "<string>",
"joiningAdminOnly": true,
"publicId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"publicAuthorizedUrls": [
"<string>"
],
"ainame": "<string>",
"contactEmail": "jsmith@example.com",
"creditAward": 123,
"poolCredits": true,
"invoices_urls": [
"<string>"
],
"maxCompletionTokens": 123,
"disableFileUploadForUser": true,
"disableAudioNotesForUser": true,
"toolsDisabled": [
"<string>"
],
"ltiContextIds": [
"<string>"
],
"personalisationAsked": true,
"locationEnabled": true,
"rtEnabled": true,
"rtAdminOnly": true,
"displayAgentDetails": true,
"displayThinkingDetails": true,
"displayRagSearchDetails": true,
"displayMemoryDetails": true,
"displayThinkingExecution": true,
"displayToolExecution": true,
"assistantsDisabled": [
"<string>"
],
"disableAssistantsForUser": true,
"rtVoice": "<string>",
"maxFiles": 123,
"questionType": "<string>",
"creditsTotal": 123,
"creditsUsagePct": 123,
"id": "<string>"
}
},
"brandingSwitch": {
"publicId": "f831501f-b645-481a-9cbb-331509aaf8c1",
"result": "switched",
"reason": "<string>"
},
"mfaRequired": true,
"challengeId": "6856fa89cbafcff8d98680f5",
"maskedEmail": "j*****e@example.com",
"mandatorySuper": true
}{
"success": false,
"code": "APPLE_TOKEN_INVALID",
"message": "Apple has not verified the email address on this Apple ID. Verify it with Apple, then try again."
}{
"success": false,
"code": "APPLE_TOKEN_INVALID",
"message": "Apple has not verified the email address on this Apple ID. Verify it with Apple, then try again."
}{
"success": false,
"code": "APPLE_TOKEN_INVALID",
"message": "Apple has not verified the email address on this Apple ID. Verify it with Apple, then try again."
}Sign in with Apple (native)
Native (iOS app) Sign in with Apple. Verifies identityToken against Apple’s public JWKS,
then resolves the user by appleSub first, falling back to a verified-email match, and
finally creates a new account — then completes through the same autosignup → signin
pipeline as /api/auth/autosignup, so the response shape, email-MFA challenge, and branding
switch all behave exactly as they do there. No OTP round-trip: the identity token itself is
the assertion (see docs/index/oauth-otp.md).
Resolution order:
- A user already carrying this
appleSub→ that user signs in (name/email untouched). - No
appleSubmatch → the token must carry a verified email (email_verified === true, checked before any lookup, so an unverified caller learns nothing about existing data) — a user already found at that email with a DIFFERENTappleSubis a conflict; otherwise the Apple id is linked onto that user. - Nothing matches → a new account is created, named from
fullNamewhen present.
Security:
- Rate limited via
authLimiter(100 requests/minute/IP). - The verified Apple identity travels on a trusted, non-enumerable request marker — never a
body field — so the public shape this route forwards to
autosignupcannot be forged by POSTing the same fields to/api/auth/autosignupdirectly. - Do not send
lticontextid— it is never forwarded, and would misroute this into SDK-launch handling if it were.
curl --request POST \
--url https://pria.praxislxp.com/api/auth/apple \
--header 'Content-Type: application/json' \
--data '
{
"identityToken": "eyJraWQiOiJXNldjT0tCIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwic3ViIjoiMDAxMjM0LmFiY2RlZi4xMjM0IiwiYXVkIjoiY29tLnByYXhpcy5wcmlhIiwiZXhwIjoxNzU3NTAwMDAwLCJpYXQiOjE3NTc0OTk0MDAsIm5vbmNlIjoiYWJjMTIzIiwiZW1haWwiOiJqYW5lLmRvZUBwcml2YXRlcmVsYXkuYXBwbGVpZC5jb20iLCJlbWFpbF92ZXJpZmllZCI6InRydWUiLCJpc19wcml2YXRlX2VtYWlsIjoidHJ1ZSJ9.signature",
"nonce": "f3a1c9e2b6d4",
"fullName": {
"givenName": "Ada",
"familyName": "Lovelace"
},
"brandingId": "pub_6430736fd62d650040420674"
}
'import requests
url = "https://pria.praxislxp.com/api/auth/apple"
payload = {
"identityToken": "eyJraWQiOiJXNldjT0tCIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwic3ViIjoiMDAxMjM0LmFiY2RlZi4xMjM0IiwiYXVkIjoiY29tLnByYXhpcy5wcmlhIiwiZXhwIjoxNzU3NTAwMDAwLCJpYXQiOjE3NTc0OTk0MDAsIm5vbmNlIjoiYWJjMTIzIiwiZW1haWwiOiJqYW5lLmRvZUBwcml2YXRlcmVsYXkuYXBwbGVpZC5jb20iLCJlbWFpbF92ZXJpZmllZCI6InRydWUiLCJpc19wcml2YXRlX2VtYWlsIjoidHJ1ZSJ9.signature",
"nonce": "f3a1c9e2b6d4",
"fullName": {
"givenName": "Ada",
"familyName": "Lovelace"
},
"brandingId": "pub_6430736fd62d650040420674"
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
identityToken: 'eyJraWQiOiJXNldjT0tCIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwic3ViIjoiMDAxMjM0LmFiY2RlZi4xMjM0IiwiYXVkIjoiY29tLnByYXhpcy5wcmlhIiwiZXhwIjoxNzU3NTAwMDAwLCJpYXQiOjE3NTc0OTk0MDAsIm5vbmNlIjoiYWJjMTIzIiwiZW1haWwiOiJqYW5lLmRvZUBwcml2YXRlcmVsYXkuYXBwbGVpZC5jb20iLCJlbWFpbF92ZXJpZmllZCI6InRydWUiLCJpc19wcml2YXRlX2VtYWlsIjoidHJ1ZSJ9.signature',
nonce: 'f3a1c9e2b6d4',
fullName: {givenName: 'Ada', familyName: 'Lovelace'},
brandingId: 'pub_6430736fd62d650040420674'
})
};
fetch('https://pria.praxislxp.com/api/auth/apple', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://pria.praxislxp.com/api/auth/apple",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'identityToken' => 'eyJraWQiOiJXNldjT0tCIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwic3ViIjoiMDAxMjM0LmFiY2RlZi4xMjM0IiwiYXVkIjoiY29tLnByYXhpcy5wcmlhIiwiZXhwIjoxNzU3NTAwMDAwLCJpYXQiOjE3NTc0OTk0MDAsIm5vbmNlIjoiYWJjMTIzIiwiZW1haWwiOiJqYW5lLmRvZUBwcml2YXRlcmVsYXkuYXBwbGVpZC5jb20iLCJlbWFpbF92ZXJpZmllZCI6InRydWUiLCJpc19wcml2YXRlX2VtYWlsIjoidHJ1ZSJ9.signature',
'nonce' => 'f3a1c9e2b6d4',
'fullName' => [
'givenName' => 'Ada',
'familyName' => 'Lovelace'
],
'brandingId' => 'pub_6430736fd62d650040420674'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://pria.praxislxp.com/api/auth/apple"
payload := strings.NewReader("{\n \"identityToken\": \"eyJraWQiOiJXNldjT0tCIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwic3ViIjoiMDAxMjM0LmFiY2RlZi4xMjM0IiwiYXVkIjoiY29tLnByYXhpcy5wcmlhIiwiZXhwIjoxNzU3NTAwMDAwLCJpYXQiOjE3NTc0OTk0MDAsIm5vbmNlIjoiYWJjMTIzIiwiZW1haWwiOiJqYW5lLmRvZUBwcml2YXRlcmVsYXkuYXBwbGVpZC5jb20iLCJlbWFpbF92ZXJpZmllZCI6InRydWUiLCJpc19wcml2YXRlX2VtYWlsIjoidHJ1ZSJ9.signature\",\n \"nonce\": \"f3a1c9e2b6d4\",\n \"fullName\": {\n \"givenName\": \"Ada\",\n \"familyName\": \"Lovelace\"\n },\n \"brandingId\": \"pub_6430736fd62d650040420674\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://pria.praxislxp.com/api/auth/apple")
.header("Content-Type", "application/json")
.body("{\n \"identityToken\": \"eyJraWQiOiJXNldjT0tCIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwic3ViIjoiMDAxMjM0LmFiY2RlZi4xMjM0IiwiYXVkIjoiY29tLnByYXhpcy5wcmlhIiwiZXhwIjoxNzU3NTAwMDAwLCJpYXQiOjE3NTc0OTk0MDAsIm5vbmNlIjoiYWJjMTIzIiwiZW1haWwiOiJqYW5lLmRvZUBwcml2YXRlcmVsYXkuYXBwbGVpZC5jb20iLCJlbWFpbF92ZXJpZmllZCI6InRydWUiLCJpc19wcml2YXRlX2VtYWlsIjoidHJ1ZSJ9.signature\",\n \"nonce\": \"f3a1c9e2b6d4\",\n \"fullName\": {\n \"givenName\": \"Ada\",\n \"familyName\": \"Lovelace\"\n },\n \"brandingId\": \"pub_6430736fd62d650040420674\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://pria.praxislxp.com/api/auth/apple")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"identityToken\": \"eyJraWQiOiJXNldjT0tCIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwic3ViIjoiMDAxMjM0LmFiY2RlZi4xMjM0IiwiYXVkIjoiY29tLnByYXhpcy5wcmlhIiwiZXhwIjoxNzU3NTAwMDAwLCJpYXQiOjE3NTc0OTk0MDAsIm5vbmNlIjoiYWJjMTIzIiwiZW1haWwiOiJqYW5lLmRvZUBwcml2YXRlcmVsYXkuYXBwbGVpZC5jb20iLCJlbWFpbF92ZXJpZmllZCI6InRydWUiLCJpc19wcml2YXRlX2VtYWlsIjoidHJ1ZSJ9.signature\",\n \"nonce\": \"f3a1c9e2b6d4\",\n \"fullName\": {\n \"givenName\": \"Ada\",\n \"familyName\": \"Lovelace\"\n },\n \"brandingId\": \"pub_6430736fd62d650040420674\"\n}"
response = http.request(request)
puts response.read_body{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2NDMwNzM2ZmQ2MmQ2NTAwNDA0MjA2NzQiLCJlbWFpbCI6ImpvaG4uZG9lQG15ZG9tYWluLmNvbSIsImN1c3RvbWVySWQiOiJjdXNfTnh4eHh4eCIsImFjY291bnRUeXBlIjoidXNlciIsInNlc3Npb25JZCI6InMlM0FhYmMxMjMiLCJpYXQiOjE3MDAwMDAwMDAsImV4cCI6MTcwMDA4NjQwMH0.signature",
"profile": {
"_id": "<string>",
"email": "jsmith@example.com",
"fname": "<string>",
"lname": "<string>",
"picture": "<string>",
"accountType": "<string>",
"permissions": [
"<string>"
],
"customerId": "<string>",
"lxp_user_id": "<string>",
"lxp_user_type": 123,
"lxp_partner_id": "<string>",
"lxp_partner_name": "<string>",
"lxp_role_id": 123,
"lxp_role_name": "<string>",
"credits": 123,
"creditsUsed": 123,
"plan": "<string>",
"status": "<string>",
"trial_end": "2023-11-07T05:31:56Z",
"trial_used": true,
"current_period_end": "2023-11-07T05:31:56Z",
"cancel_at_period_end": true,
"referralId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"referrerPaid": true,
"resetCodeId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"invoices_urls": [
"<string>"
],
"remember_history_count": 123,
"browser_voice": "<string>",
"rt_voice": "<string>",
"use_location": true,
"showSideBar": true,
"dark_mode": true,
"created": "2023-11-07T05:31:56Z",
"__v": 123,
"institution": {
"_id": "<string>",
"name": "<string>",
"picture": "<string>",
"picture_bg": "<string>",
"picture_dark_bg": "<string>",
"picture_animated": "<string>",
"elevenlabs_agent_id": "<string>",
"credits": 123,
"status": "<string>",
"allowJoining": "<string>",
"joiningAdminOnly": true,
"publicId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"publicAuthorizedUrls": [
"<string>"
],
"ainame": "<string>",
"contactEmail": "jsmith@example.com",
"creditAward": 123,
"poolCredits": true,
"invoices_urls": [
"<string>"
],
"maxCompletionTokens": 123,
"disableFileUploadForUser": true,
"disableAudioNotesForUser": true,
"toolsDisabled": [
"<string>"
],
"ltiContextIds": [
"<string>"
],
"personalisationAsked": true,
"locationEnabled": true,
"rtEnabled": true,
"rtAdminOnly": true,
"displayAgentDetails": true,
"displayThinkingDetails": true,
"displayRagSearchDetails": true,
"displayMemoryDetails": true,
"displayThinkingExecution": true,
"displayToolExecution": true,
"assistantsDisabled": [
"<string>"
],
"disableAssistantsForUser": true,
"rtVoice": "<string>",
"maxFiles": 123,
"questionType": "<string>",
"creditsTotal": 123,
"creditsUsagePct": 123,
"id": "<string>"
}
},
"brandingSwitch": {
"publicId": "f831501f-b645-481a-9cbb-331509aaf8c1",
"result": "switched",
"reason": "<string>"
},
"mfaRequired": true,
"challengeId": "6856fa89cbafcff8d98680f5",
"maskedEmail": "j*****e@example.com",
"mandatorySuper": true
}{
"success": false,
"code": "APPLE_TOKEN_INVALID",
"message": "Apple has not verified the email address on this Apple ID. Verify it with Apple, then try again."
}{
"success": false,
"code": "APPLE_TOKEN_INVALID",
"message": "Apple has not verified the email address on this Apple ID. Verify it with Apple, then try again."
}{
"success": false,
"code": "APPLE_TOKEN_INVALID",
"message": "Apple has not verified the email address on this Apple ID. Verify it with Apple, then try again."
}Body
The Apple identity token as delivered by ASAuthorizationAppleIDCredential.identityToken
(a UTF-8 string, not the raw bytes) — a short-lived RS256 JWT signed by Apple. Verified
against Apple's public JWKS (signature, issuer, audience allowlist, expiry); the raw
Apple private key is never involved. Single-use in spirit (10-minute validity) — do not
cache it client-side.
"eyJraWQiOiJXNldjT0tCIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwic3ViIjoiMDAxMjM0LmFiY2RlZi4xMjM0IiwiYXVkIjoiY29tLnByYXhpcy5wcmlhIiwiZXhwIjoxNzU3NTAwMDAwLCJpYXQiOjE3NTc0OTk0MDAsIm5vbmNlIjoiYWJjMTIzIiwiZW1haWwiOiJqYW5lLmRvZUBwcml2YXRlcmVsYXkuYXBwbGVpZC5jb20iLCJlbWFpbF92ZXJpZmllZCI6InRydWUiLCJpc19wcml2YXRlX2VtYWlsIjoidHJ1ZSJ9.signature"
Required (not optional — a replay control that can be skipped is not one). The RAW
nonce the app generated for this sign-in attempt; the app sends Apple a SHA-256 digest
of this same value. The server accepts the raw nonce or its SHA-256 as hex, base64 or
base64url, matched against the nonce claim inside identityToken.
"f3a1c9e2b6d4"
Apple sends this ONLY on the very first authorization for a given Apple ID + app pair —
send it whenever the native SDK callback provides it, or the account is created nameless
(Apple will not resend it later without the user removing and re-granting the app).
Ignored entirely when an existing user is matched by appleSub or by a verified email.
Show child attributes
Show child attributes
Same optional branding hint accepted by /api/auth/autosignup and /api/auth/signin — switches the active institution to the branded twin when the user is already a member.
"pub_6430736fd62d650040420674"
Response
Signed in (or newly created and signed in). Same union as /api/auth/signin: either
{ token, profile }, or { mfaRequired: true, challengeId, factor, availableFactors, maskedEmail } when the resolved user has email MFA enabled — discriminate on
mfaRequired === true and complete via POST /api/auth/mfa-verify.
Successful signin response shape. Two variants are returned by the
same endpoint depending on whether email MFA is required:
• JWT issued — { token, profile }. The user is signed in.
• MFA challenge — { mfaRequired: true, challengeId, maskedEmail, mandatorySuper? }.
The client must POST the 6-digit code to /api/auth/mfa/verify
with the challengeId; the verify endpoint then issues the JWT.
Discriminate via mfaRequired === true (per Phase 1 design §6.1).
Signed JWT token. Present when MFA is not required or has just been verified. Include this in subsequent API requests via the x-access-token header or Authorization Bearer header. Expires after 6 hours (configurable via JWT_VALIDITY_SEC). Automatically refreshed on profile load (sliding session).
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2NDMwNzM2ZmQ2MmQ2NTAwNDA0MjA2NzQiLCJlbWFpbCI6ImpvaG4uZG9lQG15ZG9tYWluLmNvbSIsImN1c3RvbWVySWQiOiJjdXNfTnh4eHh4eCIsImFjY291bnRUeXBlIjoidXNlciIsInNlc3Npb25JZCI6InMlM0FhYmMxMjMiLCJpYXQiOjE3MDAwMDAwMDAsImV4cCI6MTcwMDA4NjQwMH0.signature"
Show child attributes
Show child attributes
Outcome of a branded-link switch attempt. Present only when the request carried a brandingId (an institution publicId from a /login?brandingId= link). The hint never grants access: switched requires an active membership on the twin (or platform-operator status) and an active parent account. Clients clear the stored hint on switched/already and show the no-access notice on refused.
Show child attributes
Show child attributes
When true, the response is an MFA challenge — no JWT issued. Client should redirect to the MFA verify screen with the challengeId.
true
MongoDB ObjectId of the issued mfaChallenge. Only present when mfaRequired: true. POST this to /api/auth/mfa/verify alongside the 6-digit code.
"6856fa89cbafcff8d98680f5"
Partially-masked email address the verification code was sent to (for the verify-screen "code sent to …" prompt). Only present when mfaRequired: true.
"j*****e@example.com"
Phase 2 — when true, this MFA challenge was issued under
super-mandatory enforcement (MFA_SUPER_MANDATORY=true and the
user is past the rollout date). The verify screen should
render an explanatory banner and suppress the Cancel
affordance, since the user can't dismiss the flow without
enrolling. On successful verify, the server persists
user.mfaEnabled = true so the next signin follows the
normal phase-1 trusted-device path.
Only present when mfaRequired: true AND the gate fired.
Omitted (not false) otherwise — clients should default to
false when absent.
true
Was this page helpful?