---
title: "Initiate a project transfer (to a wallet, email, or owned org)"
method: POST
path: "/projects/v1/{project_id}/transfers"
tags: ["Project Transfers"]
---

# Initiate a project transfer (to a wallet, email, or owned org)

`POST /projects/v1/{project_id}/transfers`

## Request

- Base URL: `https://api.run402.com`
- URL: `https://api.run402.com/projects/v1/{project_id}/transfers`
- Auth: one of:
  - API key in header `SIGN-IN-WITH-X`
  - HTTP bearer

Transfer a project to another owner. Address the recipient by EXACTLY ONE of to_wallet (a two-party SIWX transfer completed via /accept), to_email (an email-addressed transfer, also accepted via /accept), or to_org_id (same-actor move into another org the caller already owns, completed synchronously). Supplying multiple recipient fields, or none, returns 400 VALIDATION_FAILED. Email-addressed transfers may include retain_member: { role: "developer" }; wallet/org-addressed transfers reject it. kysigned_record_id is wallet-only. Either pending party can cancel wallet/email transfers; otherwise they auto-expire at 72h. Single-pending-per-project (partial UNIQUE index) → 409 PENDING_TRANSFER_EXISTS. Auth: SIWX wallet, or a control-plane session (step-up gated). Org-addressed same-actor moves require active owner membership on both source and destination orgs; non-same-actor org transfers are reserved for a future interactive flow and currently return 403 FORBIDDEN.

## Path parameters

- `project_id` string, required: Run402 project ID. Public IDs are not UUIDs.

## Request body

- object: Provide exactly one of to_wallet, to_email, or to_org_id.
  - `to_wallet` string: Recipient wallet address (canonical-lowercase). Mutually exclusive with to_email and to_org_id; must differ from the initiator. Completed via POST /agent/v1/transfers/{transfer_id}/accept.
  - `to_email` string, email: Recipient email address. Mutually exclusive with to_wallet and to_org_id. The recipient accepts via POST /agent/v1/transfers/{transfer_id}/accept with a sign-in session whose verified email matches, choosing an org they own or creating a new one.
  - `to_org_id` string, uuid: Destination org id for a same-actor move. Mutually exclusive with to_wallet and to_email. In this version, the caller must be an active owner of both the source org and destination org; the move completes synchronously with a 200 response.
  - `billing_policy` 'migrate': Phase 1A only supports 'migrate' (single billing-migration mutation). 'inherit' returns 400 INVALID_BILLING_POLICY.
  - `message` string: Optional human-readable note carried in notification emails to both parties.
  - `retain_member` object: Email-addressed transfers only. Optional sender proposal to stay on as a developer of the recipient org after accept; materialized only when the recipient passes accept_retained_member: true.
    - `role` 'developer', required
  - `kysigned_record_id` string: Wallet-addressed transfers only. Optional KySigned attestation record id. Recorded as-is in Phase 1A; verification ships in Phase 1B.

## Response `200`

Same-actor org transfer completed synchronously. Returned only for to_org_id when the caller owns both orgs.

- object
  - `transfer_id` string, uuid
  - `project_id` string
  - `from_org_id` string, uuid
  - `to_org_id` string, uuid
  - `completed_at` string, date-time
  - `secrets_rotation_advised` true
  - `secret_names_inherited` string[]: Names of every secret currently on the project. Values transfer with the project but are never returned.
  - `secrets_count_inherited` integer
  - `github_repo_note` string
  - `ci_bindings_suspended` integer: Count of CI/OIDC (ci_oidc) bindings revoked by this move. When > 0, push-to-deploy is suspended until re-linked (run402 ci link github).
  - `ci_bindings_note` string: Present only when ci_bindings_suspended > 0: explains the CI/OIDC revocation and how to re-link.
  - `next_actions` object[]: Present only when ci_bindings_suspended > 0: a single re-link-CI action.
    - `type` string
    - `method` string
    - `path` string
    - `auth` string
    - `why` string
  - `anon_key` string: JWT for client-side access to the moved project (apikey header).
  - `service_key` string: JWT for admin access to the moved project (Authorization: Bearer).

## Other responses

- `201`: Pending wallet or email transfer created
- `400`: VALIDATION_FAILED, INVALID_RECIPIENT, or INVALID_BILLING_POLICY
- `403`: FORBIDDEN — caller is not authorized for the source project or, for to_org_id, lacks active owner membership on the destination org
- `409`: PENDING_TRANSFER_EXISTS, ALREADY_OWNED, RECIPIENT_ORGANIZATION_NOT_ACTIVE, PROJECT_NOT_TRANSFERABLE, TRANSFER_VAULT_BYO_UNSUPPORTED (the project's vault stores its source in the owner's own bucket, which a transfer cannot move), or, for a same-actor org transfer, TRANSFER_VAULT_HANDOVER_PENDING (the actor's own key does not hold the vault's source yet; a current writer's run402 repos access sync admits it)

---

[API](https://skmtc.dev/run402/apis/run402-api.md) · [All operations](https://skmtc.dev/run402/apis/run402-api/llms.txt) · [OpenAPI document](https://skmtc.dev/run402/apis/run402-api/revisions/893607557f50?raw)
