---
title: "Check ownership transfer"
method: POST
path: "/api/apps/{app_id}/ownership/transfer/preflight"
---

# Check ownership transfer

`POST /api/apps/{app_id}/ownership/transfer/preflight`

## Request

- Base URL: `https://app.base44.com`
- URL: `https://app.base44.com/api/apps/{app_id}/ownership/transfer/preflight`
- Auth: HTTP bearer

<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>

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](/api-reference/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.

<Note>This endpoint accepts a personal API key. A read-only key is refused, and workspace API keys are not accepted.</Note>

## Path parameters

- `app_id` string, required: ID of the app.

## Request body

- PreflightTransferRequest: The ownership transfer invitation to check.
  - `new_owner_email` string, required: Email address of the new owner. An invalid address comes back as a blocker.
  - `destination` 'same_workspace' | 'external_email', nullable: 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`.

## Response `200`

The result of the check.

- OwnershipTransferPreflightResponse: Whether an ownership transfer invitation can be sent, and what would transfer with the app.
  - `can_send` boolean, required: Whether the invitation can be sent (`true`) or a blocker stops it (`false`).
  - `recipient_status` string, required: 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.
  - `blockers` OwnershipTransferPreflightIssue[]: Problems that stop the invitation from being sent. It's empty when `can_send` is `true`.
    - `code` string, required: Machine-readable reason, for example `TRANSFER_ALREADY_PENDING`, `CANNOT_TRANSFER_TO_SELF`, `NOT_APP_OWNER`, or `NEW_OWNER_NOT_FOUND`.
    - `severity` string, required: Either `blocker` or `warning`.
    - `message` string, required: Human-readable explanation of the issue.
    - `field` string, nullable: Name of the input or app field the issue is about, or `null` when it isn't about one.
    - `details` object: Extra information about the issue, as a JSON object. It's usually empty.
  - `warnings` OwnershipTransferPreflightIssue[]: Things to know that don't stop the invitation.
    - `code` string, required: Machine-readable reason, for example `TRANSFER_ALREADY_PENDING`, `CANNOT_TRANSFER_TO_SELF`, `NOT_APP_OWNER`, or `NEW_OWNER_NOT_FOUND`.
    - `severity` string, required: Either `blocker` or `warning`.
    - `message` string, required: Human-readable explanation of the issue.
    - `field` string, nullable: Name of the input or app field the issue is about, or `null` when it isn't about one.
    - `details` object: Extra information about the issue, as a JSON object. It's usually empty.
  - `transfer_package` OwnershipTransferPackageSummary: Current v1 transfer package summary.
    - `mode` string: How the app transfers, which is always `transfer_as_built`.
    - `app` OwnershipTransferPreflightAppSummary, required: Safe app summary for sender-side transfer review.
      - `id` string, required: ID of the app.
      - `name` string, required: Name of the app.
      - `source_workspace_id` string, nullable: ID of the workspace the app is in now.
      - `source_owner_id` string, required: ID of the Base44 user who owns the app now.
    - `recipient_email` string, required: Email address the invitation would go to.
    - `existing_account_required` boolean: Whether the recipient needs a Base44 account to accept, which is always `true`. They can sign up after the invitation is sent.
    - `target_workspace` string: Where the app goes by default, which is always `recipient_personal_workspace`. The recipient can choose another workspace when they accept.
    - `active_integration_count` integer: Number of entries in `integrations`.
    - `integrations` OwnershipTransferIntegrationSummary[]: The app's connected integrations.
      - `integration_type` string, required: Integration type. Send it as `integration_type` in `integration_decisions`.
      - `display_name` string, required: Name of the integration as shown in the builder.
      - `status` string, required: Connection status. Either `active`, `expired`, or `connected`.
      - `can_keep_connected` boolean: Whether the integration can stay connected for the new owner (`true`), or always disconnects on acceptance because it uses the current owner's own account (`false`).
    - `secrets` OwnershipTransferSecretSummary[]: The app's secrets, without their values.
      - `secret_name` string, required: Name of the app secret. Send it as `secret_name` in `secret_decisions`.
      - `has_value` boolean: Whether the secret has a value that can transfer with the app (`true`) or has none (`false`).
    - `has_google_ads_account` boolean: Whether the app has a Google Ads account that transfers with it (`true`) or not (`false`). The new owner has to set up its billing again, and any outstanding balance has to be settled before the recipient can accept.

## Other responses

- `401`: Missing or invalid credentials.
- `403`: Your API key is read-only, you used a workspace API key, or the app's workspace isn't Enterprise and you aren't an approved partner, for an invitation outside the workspace.
- `404`: The app doesn't exist, or it's outside the workspace or apps your API key is scoped to.
- `422`: `new_owner_email` is missing, or `destination` isn't `same_workspace` or `external_email`.
- `429`: Rate limit reached. Retry later.

## Changes

> 58 revisions in range; 1 not diffed.

- **2026-10-12** `5e139db2e643`: 1 info
  - the endpoint scheme security `PersonalAccessTokenAuth` was added to the API
- **2026-09-29** `d2b7ac8beb0c`: 1 info
  - endpoint added

[Operation history](https://skmtc.dev/adexad/apis/base44-app-management-api/changes/api/apps/:app_id/ownership/transfer/preflight/post.md)

---

[API](https://skmtc.dev/adexad/apis/base44-app-management-api.md) · [All operations](https://skmtc.dev/adexad/apis/base44-app-management-api/llms.txt) · [OpenAPI document](https://skmtc.dev/adexad/apis/base44-app-management-api/revisions/fa29e915c226?raw)
