Skip to content

The update lifecycle

This page follows one update from the server’s point of view. The endpoints live under /v1/public/aether/ and need no authentication; the deployment key in the query identifies the channel.

On app start (by default), the SDK calls update_check with the deployment key, the binary version, and the hash of the package it currently runs. Current SDKs also send optional coarse telemetry dimensions when known: platform (ios or android), os_version (OS major; fuller strings are truncated to the leading major), react_native_version (major.minor), and sdk_version. Older clients omit them. The server accepts both camelCase and snake_case spellings, keeps only allowlisted shapes (or the truncated OS major), and never uses the fields to decide which update to serve. The server answers with update_info:

  • is_available: false when the device already has the latest eligible release, no enabled release targets its binary version, the deployment key does not resolve, app_version is empty, the deployment has no releases, or all of its releases are disabled. It is also false when a release that targets exactly the device’s binary is disabled and the only other match left is an older covering range: that covering range is not an eligible fallback. On a binary that never had its own release, a device outside a staged rollout can still receive an older eligible covering release. Otherwise the device gets no update when nothing eligible remains. If no device receives updates, check the deployment key in the app configuration first.
  • is_available: false also when the server selected a release but cannot re-sign its download URL. This is a serving failure, not a decision about the device. The next check tries to generate the URL again. If the blob no longer exists, restore it or release the update again.
  • is_available: true plus download_url, package_hash, is_mandatory, package_size, and the release metadata when there is something to install.
  • update_app_version: true when the newest release targets a newer binary than the device runs, meaning the fix ships through the app store, not over the air.

If a retryable database failure prevents the server from resolving the deployment key, reading its releases, or refreshing a download URL, it answers 503 instead. Wait for the Retry-After interval before checking again. The server made no update decision.

The SDK downloads the bundle, verifies it, and installs it according to the configured install mode: immediately, on the next restart, or on the next resume. It reports the download through report_status/download, which feeds the per-release download counter. The same optional dimensions travel on that report and on report_status/deploy; the server stores allowlisted values in a parallel metrics table for later analytics, while the existing per-label counters and dashboard panels stay unchanged.

After the new bundle runs, the SDK confirms it with the server through report_status/deploy. A crash before that confirmation triggers the SDK’s automatic per-device rollback: the device returns to its previous bundle and reports the deployment as failed. These reports are the DeploymentSucceeded and DeploymentFailed numbers in aether deployment history and the metrics endpoint. A failure spike right after a release is the signal to roll back server-side. Aether evaluates automatic halts from separate counts of distinct devices served and failed within the release’s current evaluation window; those thresholds need a sizeable device population, so a small app can see a spike well before the rule does. The history totals provide context, but they do not drive that decision.

Every update_check also counts the device toward your monthly active devices (MAD), using a HyperLogLog sketch per tenant and calendar month. On the free plan, going over the MAD quota throttles update checks: devices are told to retry 48 hours later and the response carries X-Aether-Mad-Status: over_quota_throttled. Details in Plans, quotas, and MAD.

The same three endpoints also exist under the deprecated CodePush-compatible paths (/v0.1/public/codepush/*, /updateCheck, /reportStatus/*). Those responses carry Deprecation: true and a Link header with rel="successor-version" pointing at the canonical path.