Outcome
Issue an app transfer code. Read the access requirements and recovery limits before confirming a live operation.
POST /api/developer-console/apps/{appId}/transfer
Before you start
Use the originally authenticated production organization and current published Developers apps:write permission. Discover and bind the actual canonical Developers workspace separately from action input. Never change organization/context as a workaround for refusal.
Shared context requires a non-suspended developer profile and can initialize a local workspace mirror/profile before later refusal. Completed onboarding is separately required by both routes. Initialization is not canonical Auth workspace creation.
These actions change access/ownership state. Review the exact source and target organizations, app, authority and actual expected effect before confirming a real mutation. All examples here are complete synthetic UNRUN wire carriers with fictional identifiers, emails and transfer code; no operation was called. Source/declaration equality is not serving-runtime or completed-transfer proof.
Treat a real transfer code as sensitive authorization material. Use any protected credential reference returned by supported tooling unchanged; do not expose or copy a live code into documentation, logs or messages. This guide shows only an invented code.
App-specific owner access in the original source workspace is required. Issuing a code is not consent to claim into any unspecified target.
Request
Action: app_topolo_developers.apps.transfer. Discover real context and bind workspace separately; validate and plan the current declared schema.
{
"appId": "app_docs_fixture"
}
Effects and limitations
The owner issues a pending transfer; ownership is not moved by issuance. requireDeveloperAppAccess enforces app/developer workspace match, app.canRead and canTransfer (actorRole owner). App editors/viewers cannot issue a transfer merely because their credential has apps:write.
The handler ignores the request body for issuance after routing appId. The broad declared input schema has many permissive unrelated fields; they do not define an editable target organization or expectedRevision for this route. Supply the discovered appId only.
A code is generated from12random characters using the helper alphabet, formatted4-4-4. The code hash is SHA-256 of its uppercase alphanumeric normalization, lowercase hex. Expiry is seven days from issuance. Metadata.transfer becomes pending with codeHash, issuance/expiry/issuer, current previousOrganization and null target/claimant; historical transferredAt/transferredByEmail may be preserved. Existing members are retained.
A new issuance replaces the pending transfer metadata, so an earlier code is no longer the current matching pending code. The source has no request idempotency key, expected revision or compare-and-swap for this replacement. Do not issue codes in parallel or automatically reissue after response loss.
updateDeveloperApp writes metadata and updated_at, then rereads and separately writes canonical_app_id when the record remains. These writes are not a transaction with the route’s read. A failure/refusal after a write is not proof of no effect. A null returned updated app produces404 after the attempted update.
HTTP200 returns {success:true,message,data:{transferCode,transferExpiresAt,appId,app}}. app is the full actor-decorated app record, not an ownership-move receipt. The source app carrier includes transfer metadata/hash, so no real output should be placed into public examples.
Issuance does not update developerId/organizationId/workspaceId, move submissions, reconcile Auth/service/catalog/entitlements, or complete financial/account migration. The subsequent claim route has additional known limits described in its guide.
Worked responses
Owner receives a new pending transfer code
Complete synthetic UNRUN HTTP200, fictional owner, app and code. The existing source fixture is an app owner in the source canonical workspace; there was no previous transfer and issuance time is fixed for illustration. This is a direct service carrier, not proof of a particular CLI secret-redaction behavior.
{
"success": true,
"message": "Developer app transfer code issued successfully",
"data": {
"transferCode": "ABCD-EFGH-JKLM",
"transferExpiresAt": "2026-10-30T00:00:00.000Z",
"appId": "app_docs_fixture",
"app": {
"id": "app_docs_fixture",
"developerId": "dev_docs_fixture",
"organizationId": "org_docs_fixture",
"workspaceId": "ws_docs_fixture",
"name": "Fixture app",
"slug": "fixture-app",
"description": "",
"category": "",
"ownerType": "third_party",
"portfolio": null,
"audience": "business",
"tenancy": "organization",
"surfaceModel": "web_app",
"distributionModel": "marketplace",
"appStatus": "draft",
"listingStatus": "not_submitted",
"pricing": {
"type": "free"
},
"version": "0.0.0",
"websiteUrl": "",
"demoUrl": "",
"repoUrl": "",
"supportEmail": "",
"registeredAt": null,
"marketplaceSubcategory": null,
"tags": [],
"screenshots": [],
"notes": "",
"metadata": {
"ownerType": "third_party",
"portfolio": null,
"audience": "business",
"tenancy": "organization",
"surfaceModel": "web_app",
"distributionModel": "marketplace",
"members": [],
"transfer": {
"status": "pending",
"codeHash": "3f63d25fcb898ecfd6c2e2322bfdb11f44fe17dd0dc359e70b7246224bb223e9",
"codeIssuedAt": "2026-10-23T00:00:00.000Z",
"codeExpiresAt": "2026-10-30T00:00:00.000Z",
"codeIssuedByEmail": "owner@example.invalid",
"claimedByEmail": null,
"targetOrganizationId": null,
"targetOrganizationSlug": null,
"previousOrganizationId": "org_docs_fixture",
"previousOrganizationSlug": "fixture-origin",
"transferredAt": null,
"transferredByEmail": null
}
},
"members": [],
"transfer": {
"status": "pending",
"codeHash": "3f63d25fcb898ecfd6c2e2322bfdb11f44fe17dd0dc359e70b7246224bb223e9",
"codeIssuedAt": "2026-10-23T00:00:00.000Z",
"codeExpiresAt": "2026-10-30T00:00:00.000Z",
"codeIssuedByEmail": "owner@example.invalid",
"claimedByEmail": null,
"targetOrganizationId": null,
"targetOrganizationSlug": null,
"previousOrganizationId": "org_docs_fixture",
"previousOrganizationSlug": "fixture-origin",
"transferredAt": null,
"transferredByEmail": null
},
"submissionId": null,
"submittedAt": null,
"reviewedAt": null,
"reviewedBy": null,
"rejectionReason": null,
"createdAt": "2026-10-21T08:00:00.000Z",
"updatedAt": "2026-10-23T00:00:00.000Z",
"actorRole": "owner",
"canRead": true,
"canEdit": true,
"canTransfer": true
}
}
}
A pending transfer exists; ownership and workspace remain unchanged. Preserve the actual code securely, expiry and original request identity.
Editor is not an app owner
Complete source-derived synthetic UNRUN HTTP403. The current app canRead is true but canTransfer is false.
{
"success": false,
"error": {
"message": "Owner access is required to transfer this developer app."
}
}
Preserve actual status, original context and request id; do not infer no initialization/write or broaden/replay the operation.
App inaccessible
Complete source-derived synthetic UNRUN HTTP404. The app is missing, not actor-readable or mismatches the selected source workspace; not global absence proof.
{
"success": false,
"error": {
"message": "Developer app not found"
}
}
Preserve actual status, original context and request id; do not infer no initialization/write or broaden/replay the operation.
Verify and recover
-
Retain the actual full result, request identity, app id, original/target organization and transfer expiry/state. Compare stored app owner/member/transfer fields through current authorized reads, not invented ids.
-
Do not automatically repeat after response loss, timeout or a refusal that may follow shared initialization or data writes. Existing owner must reconcile actual transfer/submission state and competing custody.
-
Qualify canonical workspace and downstream ownership separately. The claim source does not write workspace_id or reconcile Auth/registry/catalog/resources/financial effects. No automatic transfer-back, reissue, member restoration or cross-context retry is published as a safe inverse.
Change log
- 2026-10-23: Added source-qualified transfer access, full synthetic unrun carriers, expiry/ownership effects, non-atomic recovery and canonical-workspace limitations. No transfer code, claim, source test or business operation was executed.