curl --request POST \
--url https://flows.super.ai/api/profile/password \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"new_password": "<string>",
"current_password": "<string>"
}
'import requests
url = "https://flows.super.ai/api/profile/password"
payload = {
"new_password": "<string>",
"current_password": "<string>"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({new_password: '<string>', current_password: '<string>'})
};
fetch('https://flows.super.ai/api/profile/password', 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://flows.super.ai/api/profile/password",
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([
'new_password' => '<string>',
'current_password' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"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://flows.super.ai/api/profile/password"
payload := strings.NewReader("{\n \"new_password\": \"<string>\",\n \"current_password\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
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://flows.super.ai/api/profile/password")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"new_password\": \"<string>\",\n \"current_password\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://flows.super.ai/api/profile/password")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"new_password\": \"<string>\",\n \"current_password\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"has_password": true,
"is_sso_managed": true
}Change the authenticated user's password
Verify the current password, then write the new one (FLO-2735 G-2).
The verification is unconditional for an account that has a password, which is
why this is a server endpoint and not a supabase.auth.updateUser call: GoTrue
skips its own current-password check for any session whose AMR is otp,
magiclink or recovery, and every email-link sign-in produces one. See the
module docstring in password_service.py for the full reasoning, including what
this does and does not protect against.
The caller’s bearer token is forwarded to GoTrue for the write itself so that session bookkeeping matches the direct client call this replaces: this device stays signed in, every other one is signed out.
NOT used by /set-password (invited user) or /reset-password (recovery).
Those hold a session minted seconds earlier by the emailed credential, have no
current password to give, and keep calling Supabase directly.
curl --request POST \
--url https://flows.super.ai/api/profile/password \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"new_password": "<string>",
"current_password": "<string>"
}
'import requests
url = "https://flows.super.ai/api/profile/password"
payload = {
"new_password": "<string>",
"current_password": "<string>"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({new_password: '<string>', current_password: '<string>'})
};
fetch('https://flows.super.ai/api/profile/password', 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://flows.super.ai/api/profile/password",
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([
'new_password' => '<string>',
'current_password' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"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://flows.super.ai/api/profile/password"
payload := strings.NewReader("{\n \"new_password\": \"<string>\",\n \"current_password\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
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://flows.super.ai/api/profile/password")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"new_password\": \"<string>\",\n \"current_password\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://flows.super.ai/api/profile/password")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"new_password\": \"<string>\",\n \"current_password\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"has_password": true,
"is_sso_managed": true
}Authorizations
JWT Bearer token authentication. Include your access token in the Authorization header as: Bearer YOUR_ACCESS_TOKEN
Example:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Body
Change (or first set) the authenticated user's password (FLO-2735 G-2).
current_password is required by the server whenever the account has one, and
must be absent-or-anything when it does not. It is deliberately not marked
required here: the schema cannot see which case applies, and a 422 on a field the
caller had no way to fill would be a worse answer than the current_password_required
code the service returns.
The replacement password. Strength is validated by Supabase Auth, which owns the password policy, so the only bounds applied here are the lengths GoTrue itself will not accept outside.
8 - 72The account's existing password. Required when the account has one; verified server-side before the new password is written. Omit for an account that has no password yet (a magic-link or freshly invited user).
1 - 128Response
Password updated
What the account-settings password form is allowed to render (FLO-2735 G-2).
Both fields are read live from auth.users on every call rather than folded
into GET /profile/me. Two reasons: the profile response is fetched on every
page load and this is needed on exactly one screen, and has_password changes
the moment a first password is set — a value cached alongside the profile would
keep showing "Set a password" to someone who just set one.
Whether the Supabase Auth record already has a password. Mirrors the user.HasPassword() guard GoTrue applies before enforcing Require Current Password, so the form asks for a current password exactly when GoTrue would demand one. False for a magic-link-only or freshly invited account, which sets its first password with no current password to give.
Whether the Supabase Auth record is SSO-managed (auth.users.is_sso_user). GoTrue refuses email/phone/password updates for these outright with user_sso_managed, so the form is not rendered at all rather than offered and then rejected. Defaults false so an unknown state still shows a form whose failure is at least explained.