curl --request POST \
--url https://app.base44.com/api/apps/{app_id}/ownership/transfer/preflight \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"new_owner_email": "jane@acme.com",
"destination": "external_email"
}
'import requests
url = "https://app.base44.com/api/apps/{app_id}/ownership/transfer/preflight"
payload = {
"new_owner_email": "jane@acme.com",
"destination": "external_email"
}
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_owner_email: 'jane@acme.com', destination: 'external_email'})
};
fetch('https://app.base44.com/api/apps/{app_id}/ownership/transfer/preflight', 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://app.base44.com/api/apps/{app_id}/ownership/transfer/preflight",
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_owner_email' => 'jane@acme.com',
'destination' => 'external_email'
]),
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://app.base44.com/api/apps/{app_id}/ownership/transfer/preflight"
payload := strings.NewReader("{\n \"new_owner_email\": \"jane@acme.com\",\n \"destination\": \"external_email\"\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://app.base44.com/api/apps/{app_id}/ownership/transfer/preflight")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"new_owner_email\": \"jane@acme.com\",\n \"destination\": \"external_email\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.base44.com/api/apps/{app_id}/ownership/transfer/preflight")
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_owner_email\": \"jane@acme.com\",\n \"destination\": \"external_email\"\n}"
response = http.request(request)
puts response.read_body{
"can_send": true,
"recipient_status": "can_send",
"blockers": [
{
"code": "TRANSFER_ALREADY_PENDING",
"severity": "blocker",
"message": "A pending ownership transfer already exists for Acme CRM.",
"field": "new_owner_email",
"details": {}
}
],
"warnings": [
{
"code": "TRANSFER_ALREADY_PENDING",
"severity": "blocker",
"message": "A pending ownership transfer already exists for Acme CRM.",
"field": "new_owner_email",
"details": {}
}
],
"transfer_package": {
"app": {
"id": "6820f3a4e7b91d003c45a1f2",
"name": "Acme CRM",
"source_owner_id": "6820f3a4e7b91d003c45a1f0",
"source_workspace_id": "67f2c8e01a3b5d004e92d7a1"
},
"recipient_email": "jane@acme.com",
"mode": "transfer_as_built",
"existing_account_required": true,
"target_workspace": "recipient_personal_workspace",
"active_integration_count": 1,
"integrations": [
{
"integration_type": "slack",
"display_name": "Slack",
"status": "active",
"can_keep_connected": true
}
],
"secrets": [
{
"secret_name": "STRIPE_API_KEY",
"has_value": true
}
],
"has_google_ads_account": false
}
}Check ownership transfer
Checks whether an ownership transfer invitation can be sent to an email address, and lists what would transfer with the app. Nothing is sent or changed.
Read can_send for the answer. A problem such as an app that already has a pending transfer comes back in blockers rather than as an error, and the same problem stops Start ownership transfer. A recipient without a Base44 account yet is only a warning, since they can sign up before they accept.
Use transfer_package to choose the integration_decisions and secret_decisions you send with the invitation.
You need to own the app or be an owner or admin of its workspace. A workspace admin who doesn’t own the app also needs an Enterprise workspace or approved partner status. An invitation to someone outside the workspace needs that status too, and only workspace owners and admins can send one. The workspace’s transfer policy can narrow that to owners, or turn it off. If you can’t manage ownership transfers for this app, the result has a NOT_APP_OWNER blocker. For an invitation outside the workspace, a missing Enterprise or partner status is rejected instead.
This is limited to 60 requests a minute per workspace. Some workspaces have a different limit.
curl --request POST \
--url https://app.base44.com/api/apps/{app_id}/ownership/transfer/preflight \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"new_owner_email": "jane@acme.com",
"destination": "external_email"
}
'import requests
url = "https://app.base44.com/api/apps/{app_id}/ownership/transfer/preflight"
payload = {
"new_owner_email": "jane@acme.com",
"destination": "external_email"
}
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_owner_email: 'jane@acme.com', destination: 'external_email'})
};
fetch('https://app.base44.com/api/apps/{app_id}/ownership/transfer/preflight', 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://app.base44.com/api/apps/{app_id}/ownership/transfer/preflight",
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_owner_email' => 'jane@acme.com',
'destination' => 'external_email'
]),
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://app.base44.com/api/apps/{app_id}/ownership/transfer/preflight"
payload := strings.NewReader("{\n \"new_owner_email\": \"jane@acme.com\",\n \"destination\": \"external_email\"\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://app.base44.com/api/apps/{app_id}/ownership/transfer/preflight")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"new_owner_email\": \"jane@acme.com\",\n \"destination\": \"external_email\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.base44.com/api/apps/{app_id}/ownership/transfer/preflight")
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_owner_email\": \"jane@acme.com\",\n \"destination\": \"external_email\"\n}"
response = http.request(request)
puts response.read_body{
"can_send": true,
"recipient_status": "can_send",
"blockers": [
{
"code": "TRANSFER_ALREADY_PENDING",
"severity": "blocker",
"message": "A pending ownership transfer already exists for Acme CRM.",
"field": "new_owner_email",
"details": {}
}
],
"warnings": [
{
"code": "TRANSFER_ALREADY_PENDING",
"severity": "blocker",
"message": "A pending ownership transfer already exists for Acme CRM.",
"field": "new_owner_email",
"details": {}
}
],
"transfer_package": {
"app": {
"id": "6820f3a4e7b91d003c45a1f2",
"name": "Acme CRM",
"source_owner_id": "6820f3a4e7b91d003c45a1f0",
"source_workspace_id": "67f2c8e01a3b5d004e92d7a1"
},
"recipient_email": "jane@acme.com",
"mode": "transfer_as_built",
"existing_account_required": true,
"target_workspace": "recipient_personal_workspace",
"active_integration_count": 1,
"integrations": [
{
"integration_type": "slack",
"display_name": "Slack",
"status": "active",
"can_keep_connected": true
}
],
"secrets": [
{
"secret_name": "STRIPE_API_KEY",
"has_value": true
}
],
"has_google_ads_account": false
}
}Authorizations
Personal access token, sent as Authorization: Bearer <token>.
Path Parameters
ID of the app.
Body
The ownership transfer invitation to check.
Email address of the new owner. An invalid address comes back as a blocker.
"jane@acme.com"
Either same_workspace, to invite an owner, admin, editor, or member of the app's workspace, or external_email, to invite anyone. Defaults to external_email.
same_workspace, external_email "external_email"
Response
The result of the check.
Whether an ownership transfer invitation can be sent, and what would transfer with the app.
Whether the invitation can be sent (true) or a blocker stops it (false).
true
Summary of the result. Either can_send, recipient_must_sign_in_first when the recipient doesn't have a Base44 account yet but the invitation can still be sent, invalid_email, or blocked_policy for any other blocker.
"can_send"
Problems that stop the invitation from being sent. It's empty when can_send is true.
Show child attributes
Show child attributes
Things to know that don't stop the invitation.
Show child attributes
Show child attributes
What would transfer with the app, or null when you can't manage ownership transfers for this app.
Show child attributes
Show child attributes
Was this page helpful?