Topolo Docs Topolo Platform
Open workspace

Save mobile-artifact metadata

Create or update a mobile-artifact record by app, platform and release track, with exact defaults and metadata-only validation limits.

Outcome

Save mobile-artifact metadata for an app and release track. The same key updates the existing record; it does not upload or install a build.

POST /api/developer-console/apps/{appId}/mobile-artifacts

Before you start

Use the original authenticated organization, current Developers apps:write permission and discovered canonical workspace bound separately from input. The handler requires non-suspended developer context, completed onboarding and app.canRead/canEdit with app/workspace match; an app viewer or a broad credential alone is insufficient.

The action saves metadata for one app/platform/releaseTrack combination. It neither uploads a binary nor checks that a URL contains a signed APK/IPA, verifies a supplied checksum against bytes, installs a device build or proves store distribution. Review concrete status/channel/URL changes before confirming a real call.

Examples are complete source-derived synthetic UNRUN carriers with fictional ids/package/timestamps and a single known artifact row. No app, binary, provider, device, catalog publication, test/import or business operation was invoked. Source/declaration equality does not prove serving runtime.

Request

Action: app_topolo_developers.apps.mobile_artifacts.create. Discover real app/workspace and validate/plan the current schema before a separately authorized real mutation.

{
  "appId": "app_docs_fixture",
  "platform": "android",
  "versionName": "1.0.0",
  "artifactType": "metadata",
  "packageName": "invalid.example.fixture",
  "supportedDeviceClasses": [
    "phone"
  ]
}

Effects and defaults

HTTP200 returns nested data:{artifact,artifacts}. artifact is the reread upsert row (can be null if concurrently absent); artifacts is a separate subsequent list for the app, ordered platform ascending, release_track ascending, updated_at descending. They are not a single atomic snapshot. Both use the formatter’s21fields and omit stored workspace_id.

Despite the action name create, the logical key is appId/platform/releaseTrack. The helper first reads that key; it inserts a new mart_ id if absent and SQL ON CONFLICT updates the existing row. Existing id/created_at stay unchanged on conflict; there is no expectedRevision/CAS. A stale read can carry old defaults into a later upsert.

platform is trimmed/lowercased. releaseTrack is trimmed/lowercased and defaults to internal. The declared schema requires appId,platform,versionName,artifactType even though the raw helper has additional defaults; do not send undeclared fields or bypass stricter published schema validation.

packageName/bundleId/versionName/buildNumber/artifactType/artifactUrl/checksumSha256/distributionChannel/catalogCategory/status/minimumOsVersion generally fall back through existing artifact then helper default when input is null/absent. Version/build/minimum-OS are trimmed/truncated40characters; distribution channel60. Native schemas may reject values the raw helper would coerce or trim.

supportedDeviceClasses is different: omitted input becomes an empty list, replacing saved device classes. Raw helper accepts an array or comma-separated string, stringifies/trims entries, drops empties and retains first8; the declared action permits only an array of up to8nonempty strings. metadata uses an input object if present, otherwise existing metadata or {}; it is not deep-merged.

Source validates platform android/ios; nonempty releaseTrack up to60characters; nonempty versionName; artifactType metadata/apk/aab/ipa/app_store/testflight; status draft/submitted/approved/published/retired; exact catalogCategory system-apps/topolo-store/topolo-apps/apkList. Android requires a nonempty packageName, iOS a nonempty bundleId. These are not package/signature/Apple ownership verification.

Optional artifactUrl must parse as HTTP or HTTPS in source; a declared generic URI format alone is not enough. Optional checksum is normalized lowercase and must be64hexcharacters; no download/digest comparison occurs. The helper does not enforce a platform↔artifactType pairing or require a URL/checksum just because the caller sets a non-metadata artifactType/status.

After field validation, the helper requires app.workspaceId and a nonempty app.organizationId, then ensures a local mirror in the artifact database before the upsert. The stored workspace/organization/developer fields come from the app, not caller-supplied overrides. Shared developer context may also initialize its own mirror/profile before some later refusal.

Storage/mirror creation/upsert/reread/list are separate operations. A thrown post-write query or list failure can surface HTTP500 after a stored change. Do not equate a failed response with rollback, or blindly replay. No transaction, atomic completion receipt or downstream deployment proof is provided here.

A recorded approved/published status is only this metadata row’s value. Source does not publish the platform app catalog, submit an external store review or install/execute an artifact. Public visibility and actual binary trust require their own current contracts/owners.

Worked responses

Create Android metadata for the internal track

Complete source-derived synthetic UNRUN HTTP200. No row exists for the selected app/android/internal key; the source creates one. The fictional app has exactly this one artifact after the write. Fictional app, ids and package; no upload, install, review or delivery was performed.

{
  "success": true,
  "message": "Mobile artifact saved successfully",
  "data": {
    "artifact": {
      "id": "mart_deadbeef0012",
      "appId": "app_docs_fixture",
      "developerId": "dev_docs_fixture",
      "organizationId": "org_docs_fixture",
      "platform": "android",
      "packageName": "invalid.example.fixture",
      "bundleId": "",
      "versionName": "1.0.0",
      "buildNumber": "",
      "artifactType": "metadata",
      "artifactUrl": "",
      "checksumSha256": "",
      "releaseTrack": "internal",
      "distributionChannel": "managed_catalog",
      "catalogCategory": "topolo-apps",
      "status": "draft",
      "minimumOsVersion": "",
      "supportedDeviceClasses": [
        "phone"
      ],
      "metadata": {},
      "createdAt": "2026-10-23T00:00:00.000Z",
      "updatedAt": "2026-10-23T00:00:00.000Z"
    },
    "artifacts": [
      {
        "id": "mart_deadbeef0012",
        "appId": "app_docs_fixture",
        "developerId": "dev_docs_fixture",
        "organizationId": "org_docs_fixture",
        "platform": "android",
        "packageName": "invalid.example.fixture",
        "bundleId": "",
        "versionName": "1.0.0",
        "buildNumber": "",
        "artifactType": "metadata",
        "artifactUrl": "",
        "checksumSha256": "",
        "releaseTrack": "internal",
        "distributionChannel": "managed_catalog",
        "catalogCategory": "topolo-apps",
        "status": "draft",
        "minimumOsVersion": "",
        "supportedDeviceClasses": [
          "phone"
        ],
        "metadata": {},
        "createdAt": "2026-10-23T00:00:00.000Z",
        "updatedAt": "2026-10-23T00:00:00.000Z"
      }
    ]
  }
}

Compare actual full artifact and complete artifacts list for the app, platform and releaseTrack. Stored metadata status is not binary verification, app review, installation or public distribution proof.

Same key updates the existing record and clears omitted device classes

Complete source-derived synthetic UNRUN HTTP200. The existing row is the prior fictional example and remains the only row. packageName is retained from the existing artifact; supportedDeviceClasses is omitted and becomes [], not the old [phone]. id/createdAt remain stable while versionName/updatedAt change. Fictional app, ids and package; no upload, install, review or delivery was performed.

{
  "success": true,
  "message": "Mobile artifact saved successfully",
  "data": {
    "artifact": {
      "id": "mart_deadbeef0012",
      "appId": "app_docs_fixture",
      "developerId": "dev_docs_fixture",
      "organizationId": "org_docs_fixture",
      "platform": "android",
      "packageName": "invalid.example.fixture",
      "bundleId": "",
      "versionName": "1.1.0",
      "buildNumber": "",
      "artifactType": "metadata",
      "artifactUrl": "",
      "checksumSha256": "",
      "releaseTrack": "internal",
      "distributionChannel": "managed_catalog",
      "catalogCategory": "topolo-apps",
      "status": "draft",
      "minimumOsVersion": "",
      "supportedDeviceClasses": [],
      "metadata": {},
      "createdAt": "2026-10-23T00:00:00.000Z",
      "updatedAt": "2026-10-23T01:00:00.000Z"
    },
    "artifacts": [
      {
        "id": "mart_deadbeef0012",
        "appId": "app_docs_fixture",
        "developerId": "dev_docs_fixture",
        "organizationId": "org_docs_fixture",
        "platform": "android",
        "packageName": "invalid.example.fixture",
        "bundleId": "",
        "versionName": "1.1.0",
        "buildNumber": "",
        "artifactType": "metadata",
        "artifactUrl": "",
        "checksumSha256": "",
        "releaseTrack": "internal",
        "distributionChannel": "managed_catalog",
        "catalogCategory": "topolo-apps",
        "status": "draft",
        "minimumOsVersion": "",
        "supportedDeviceClasses": [],
        "metadata": {},
        "createdAt": "2026-10-23T00:00:00.000Z",
        "updatedAt": "2026-10-23T01:00:00.000Z"
      }
    ]
  }
}

Compare actual full artifact and complete artifacts list for the app, platform and releaseTrack. Stored metadata status is not binary verification, app review, installation or public distribution proof.

Android package name missing on a new row

Complete synthetic UNRUN HTTP422 with no existing app/android/internal row and an otherwise valid app/context. The declared schema permits omission but the helper rejects empty Android packageName before artifact mirror/insert. Earlier shared context may still have initialized metadata.

{
  "success": false,
  "error": {
    "message": "Please review the mobile artifact fields.",
    "details": {
      "packageName": true
    }
  }
}

Correct only actual reported validation fields after reviewing current existing state. Do not infer that no earlier context initialization occurred.

App not readable in selected workspace

Complete synthetic UNRUN HTTP404. The app is missing, not actor-readable or mismatches the current canonical Developers workspace. This is not global app absence proof.

{
  "success": false,
  "error": {
    "message": "Developer app not found"
  }
}

Preserve original authorization/context and actual request identity; no guessed app or alternate organization as fallback.

Verify and recover

  1. Discover actual current app and workspace before planning; retain the app/platform/releaseTrack key and actual id/status/hash/URL. Compare the returned artifact and subsequent app artifacts list, recognizing separate read times.

  2. For an uncertain result, use the supported current artifact-list read under the same original scope and compare all intended fields plus unchanged id/createdAt. Preserve current competing updates and request identity; unchanged or missing data is not by itself proof no intermediate effect occurred.

  3. Before an intentional correction, reread the full current record and explicitly retain or change supportedDeviceClasses/metadata; do not accidentally clear classes or replace metadata. Obtain exact mutation authority; no automatic delete, retire, upload, install or owner/context switch is a read-verification or safe inverse.

Change log

  • 2026-10-23: Added full metadata upsert/list carriers, logical-key/default behavior, omitted device-class clearing, extra Android validation and partial-write recovery limits. All examples synthetic unrun; no artifact or deployment operation performed.