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>",
"account": {
"_id": "<string>",
"name": "<string>",
"status": "<string>",
"canvasCredsResolved": true,
"privacyPolicyHTML": "<string>",
"privacyPolicyUpdatedAt": "2023-11-07T05:31:56Z",
"privacyPolicyHash": "3bfb489f9bd077f0"
}
}
},
"brandingSwitch": {
"publicId": "f831501f-b645-481a-9cbb-331509aaf8c1",
"result": "switched",
"reason": "<string>"
},
"mfaRequired": true,
"challengeId": "6856fa89cbafcff8d98680f5",
"maskedEmail": "j*****e@example.com",
"codeSent": false,
"stepUp": 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>",
"account": {
"_id": "<string>",
"name": "<string>",
"status": "<string>",
"canvasCredsResolved": true,
"privacyPolicyHTML": "<string>",
"privacyPolicyUpdatedAt": "2023-11-07T05:31:56Z",
"privacyPolicyHash": "3bfb489f9bd077f0"
}
}
},
"brandingSwitch": {
"publicId": "f831501f-b645-481a-9cbb-331509aaf8c1",
"result": "switched",
"reason": "<string>"
},
"mfaRequired": true,
"challengeId": "6856fa89cbafcff8d98680f5",
"maskedEmail": "j*****e@example.com",
"codeSent": false,
"stepUp": 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, codeSent?, stepUp? }.
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"
false when the challenge comes from an SDK / Canvas launch (/api/auth/autosignup with
institution_id or lticontextid): no code
has been emailed yet. Show a "Send me a code" action that calls /api/auth/mfa-resend
(the first send is not subject to the 30-second wait), then the code entry. A repeated
launch of the same Digital Twin and course from the same network within the challenge's
lifetime answers with the same challengeId, so a code already emailed keeps working; a
launch of another Digital Twin or course, or from another network, gets its own
challenge (a network keeps at most three pending launch challenges of a person — a
fourth replaces the oldest). Absent or true means the code was emailed.
false
true when the sign-in asks for a code before any session is opened — an embedded /
Canvas launch, or a provider sign-in, of an account that needs a second proof first.
Comes with codeSent: false: show "Send me a code" (/api/auth/mfa-resend), then the
code entry; /api/auth/mfa-verify then signs the person in — on a launch, to the
launched Digital Twin and course. Absent or false otherwise.
true
Was this page helpful?