Account
Everything here is called as the signed-in user: X-App-Key plus Authorization: Bearer <access token>.
The examples use the authorized helper from Sign-in and tokens.
Profile
GET /me returns the user; PATCH /me changes first_name and last_name (both optional; null
clears one) and returns the user.
const { data: user } = await authorized('PATCH', '/me', { first_name: 'Jane', last_name: 'Doe' });
Profile photo
POST /me/photo uploads a photo as multipart field photo (JPEG, PNG, WebP or GIF, within the app's
file limits). The photo is public; its stable URL is the user's photo_url and the picture claim of
new access tokens. DELETE /me/photo removes it. To use an image the user already uploaded
(Files), send PATCH /me with {"photo_file_id": "<file id>"}.
// Flutter
final request = http.MultipartRequest('POST', Uri.parse('$baseUrl/me/photo'))
..headers.addAll({'X-App-Key': appKey, 'Accept': 'application/json', 'Authorization': 'Bearer $accessToken'})
..files.add(await http.MultipartFile.fromPath('photo', imagePath));
final response = await http.Response.fromStream(await request.send());
// React Native
const form = new FormData();
form.append('photo', { uri: image.uri, name: 'photo.jpg', type: 'image/jpeg' } as any);
const response = await fetch(`${BASE_URL}/me/photo`, {
method: 'POST',
headers: { 'X-App-Key': APP_KEY, Accept: 'application/json', Authorization: `Bearer ${accessToken}` },
body: form, // fetch sets the multipart Content-Type and boundary
});
// iOS
var request = URLRequest(url: baseURL.appendingPathComponent("me/photo"))
request.httpMethod = "POST"
let boundary = UUID().uuidString
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
request.setValue(appKey, forHTTPHeaderField: "X-App-Key")
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")
var body = Data()
body.append("--\(boundary)\r\nContent-Disposition: form-data; name=\"photo\"; filename=\"photo.jpg\"\r\nContent-Type: image/jpeg\r\n\r\n".data(using: .utf8)!)
body.append(jpegData)
body.append("\r\n--\(boundary)--\r\n".data(using: .utf8)!)
let (data, _) = try await URLSession.shared.upload(for: request, from: body)
// Android: OkHttp
val body = MultipartBody.Builder().setType(MultipartBody.FORM)
.addFormDataPart("photo", "photo.jpg", file.asRequestBody("image/jpeg".toMediaType()))
.build()
val request = Request.Builder().url("$BASE_URL/me/photo")
.header("X-App-Key", APP_KEY).header("Accept", "application/json")
.header("Authorization", "Bearer $accessToken")
.post(body).build()
http.newCall(request).execute().use { response -> /* JSONObject(response.body!!.string()) */ }
// Web
const form = new FormData();
form.append('photo', fileInput.files![0]);
const response = await fetch(`${BASE_URL}/me/photo`, {
method: 'POST',
headers: { 'X-App-Key': APP_KEY, Accept: 'application/json', Authorization: `Bearer ${accessToken}` },
body: form,
});
Verifying the email address
Apps can require a verified email for their features (the require_verified_email setting, shown by
GET /app); other calls then answer 403 auth/email-not-verified.
POST /auth/email/verification/sendemails a 6-digit code (valid 10 minutes).POST /auth/email/verification/confirmwith{"code"}marks the address verified and returns the user.- Refresh the tokens so the access token says
email_verified: true.
Sign-in with Google, Apple or an email code gives a verified address from the start.
Sensitive actions
Deleting the account, changing the email and linking or unlinking sign-in methods need a sign-in in
the last 5 minutes. Otherwise they answer 403 auth/requires-recent-login: ask for the password and
call POST /auth/reauthenticate with {"password"}. It returns a new access token (the refresh token
stays); then retry. Users without a password sign in again with their provider instead.
try {
await authorized('DELETE', '/me');
} catch (e) {
if (e instanceof ApiError && e.code === 'auth/requires-recent-login') {
const { data } = await authorized('POST', '/auth/reauthenticate', { password: await askForPassword() });
accessToken = data.access_token;
await authorized('DELETE', '/me');
} else throw e;
}
Changing the password
PUT /me/password with {"current_password", "password"} (204). The user's other sessions end;
this one stays. A wrong current password answers 400 auth/invalid-credential.
Changing the email address
POST /me/emailwith{"email"}(needs a recent sign-in) emails a code to the new address.409 auth/email-already-in-useif another account of the app has it.POST /me/email/confirmwith{"email", "code"}switches to the new, verified, address and returns the user.
Sign-in methods
GET /me/identities lists how the user can sign in: password, google, apple.
POST /me/identities/passwordwith{"password"}adds a password (for an account created with Google, Apple or an email code).POST /me/identities/googlewith{"id_token", "nonce"}andPOST /me/identities/applewith{"identity_token", "nonce", "authorization_code"}link those providers.409 auth/credential-already-in-usewhen that Google or Apple account signs in to another user.DELETE /me/identities/{provider}unlinks one; the last method can't be removed (409 auth/last-sign-in-method).
Signed-in devices
GET /me/sessions lists the user's sessions (current marks this one) with the sign-in method, times,
IP address and user agent. DELETE /me/sessions/{id} signs that device out.
Deleting the account
DELETE /me (needs a recent sign-in; optional {"reason"}) deletes the account: every session ends,
its files and private documents are deleted, stored Apple tokens are revoked, and the email can sign up
again. It answers 204. Offer it in the app: the App Store and Google Play require account deletion
in apps that let users create accounts.