Outcome
Remove the saved native mobile descriptor from an app’s control metadata, keeping that result separate from catalogue publication and device delivery.
DELETE /api/developer-console/apps/{appId}/native-mobile-experience
Before you start
Discover the accessible canonical app and Developers workspace in the original organization. Bind that workspace separately with the currently required write access and apps:write permission. Read and retain the current app metadata and intended descriptor before authorizing this destructive, confirmed mutation.
The source resolves a non-suspended/non-revoked context, requires completed Developers onboarding, then checks app/workspace and actor write access. A current app.canEdit flag alone is not future authority; no alternate workspace or additional schema field supplies a grant.
All worked examples are complete source-derived synthetic UNRUN carriers with fictional ids. No mobile descriptor, app, private record, catalogue, provider or device was changed, and no application import/test or runtime/UI probe was executed.
Request and effects
Action: app_topolo_developers.apps.native_mobile_experience.delete.
DELETE removes mobileExperience and mobileExperienceSource from the normalized app metadata, stores that full metadata value, updates the app row timestamp and then reloads/decorates the app for the current actor. It does not delete the app, APK/IPA artifact, source repository or device installation.
Only appId is needed by this DELETE; the route does not parse a request body. The current broad published schema admits many unrelated optional fields, but that does not make them implemented update controls. Send the minimal discovered appId and separately bound workspace; do not assume descriptor/source/action fields change this operation.
The complete HTTP200 result is {success:true,message,data:{app}}. Its app is the decorated control-record projection, without the p2pCapabilities and apiKeyResources collections appended by apps.get. Saved flags and timestamps are not a delivery/installation or running UI certificate.
Normalization retains ordinary metadata and supplies canonical classification/member/transfer defaults, strips the four retired service identity aliases at the root and registration level, and can remove empty registration. Thus this is not a byte-for-byte preservation or a narrowly atomic JSON-key delete.
The helper performs an UPDATE by app id with the complete computed metadata and then reads the row; it has no expectedRevision/ETag or prior-metadata compare-and-swap. It separately writes canonical_app_id after a successful read. Concurrent changes can be lost, and a later read/canonical-id failure can occur after the metadata write. Do not infer rollback from a failed response.
There is no call to the app catalogue publisher in this route. Saved descriptor removal does not establish removal from an already published catalogue or from a running native app. Any such release or restoration needs its separate current supported operation, authority and verification.
When both descriptor keys are already absent, a repeated authorized DELETE can still rewrite normalized metadata and updated_at. Similar final key absence does not make arbitrary replay safe in the presence of concurrent metadata writers.
Worked requests and responses
Remove a saved mobile descriptor
Complete synthetic UNRUN HTTP200, assuming completed onboarding/permission/app-workspace checks and a control record with a saved mobileExperience plus mobileExperienceSource, otherwise exactly the fictional app shown. After successful removal neither key is present. Child capabilities/resource collections are not part of this response.
Input:
{
"appId": "app_docs_fixture"
}
Response:
{
"success": true,
"message": "Developer app updated successfully",
"data": {
"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": null
},
"members": [],
"transfer": null,
"submissionId": null,
"submittedAt": null,
"reviewedAt": null,
"reviewedBy": null,
"rejectionReason": null,
"createdAt": "2026-10-21T08:00:00.000Z",
"updatedAt": "2026-10-21T08:00:00.000Z",
"actorRole": "owner",
"canRead": true,
"canEdit": true,
"canTransfer": true
}
}
}
Read the same app in the original workspace, verify both keys are absent and compare all retained metadata. No catalogue or device removal is proven.
The descriptor is already absent
Complete synthetic UNRUN HTTP200 for a separately authorized operation against the already-absent fixture state, assuming no concurrent change. The app timestamp advances; this is not a guarantee that retries or concurrent full-metadata writes are harmless.
Input:
{
"appId": "app_docs_fixture"
}
Response:
{
"success": true,
"message": "Developer app updated successfully",
"data": {
"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": null
},
"members": [],
"transfer": null,
"submissionId": null,
"submittedAt": null,
"reviewedAt": null,
"reviewedBy": null,
"rejectionReason": null,
"createdAt": "2026-10-21T08:00:00.000Z",
"updatedAt": "2026-10-21T08:01:00.000Z",
"actorRole": "owner",
"canRead": true,
"canEdit": true,
"canTransfer": true
}
}
}
Confirm exact current metadata and updated state rather than assuming this is a no-write response.
Incomplete onboarding is refused
Complete synthetic UNRUN rawHTTP403 after context resolution when developer.onboardingState is not complete. This descriptor write is not reached; possible earlier shared context initialization is separate.
Input:
{
"appId": "app_docs_fixture"
}
Response:
{
"success": false,
"error": {
"message": "Complete onboarding before updating app records in the developer console."
}
}
Respect the refusal and use the legitimate onboarding/owner path; do not switch contexts or invent additional authority.
Verify and recover
-
Use apps.get with the original app/workspace and its separate read permission. Verify metadata.mobileExperience and metadata.mobileExperienceSource are absent and compare the other intended metadata/ownership/member fields. The read may append other collections, so distinguish that from this DELETE result.
-
A control read proves saved metadata only. If catalogue/runtime removal is required, use the separately authorized current release path and actual catalogue/device read-back; do not describe this action alone as a deployment or installation change.
-
After an interrupted or failed response, read the exact current app metadata and reconcile concurrent edits and the original authorized intent before any further write. Preserve uncertain effects; do not blindly repeat a full-metadata rewrite.
-
There is no automatic inverse in this DELETE. Any deliberate restoration requires the original reviewed descriptor, current source/schema validation, appropriate write authority and the separately supported update operation; never resurrect stale metadata over another owner’s changes.
Change log
- 2026-10-21: Added descriptor-only control effects, exact decorated app response, onboarding refusal and concurrency/normalization/release boundaries, with complete synthetic unrun examples.