---
title: "Admin User Guide"
description: "Using the Hands admin console: apps, releases, builds, access, and troubleshooting."
category: "Console"
order: 8
---

# Admin User Guide

Hands is a release and update distribution console for apps that need controlled binary delivery. Use the admin console to create apps, upload builds, publish releases with staged rollouts, manage share links and feedback tickets, and give teammates or automation the access they need.

## Sign in

Open the Hands site and use Login with Raft. After sign-in, Hands drops you into your first app (or the app creation wizard if none exist). The left rail navigates between Apps, Org, and your account menu (Settings, Logout).

If you can sign in but do not see an expected app, ask an organization admin to grant you app access or make the app visible to your Raft server.

## Apps

Each app has a left sidebar with its sections; the app switcher at the top of the sidebar jumps between apps, creates new ones, or returns to the full list.

- Overview: current app metadata and recent operations.
- Channels: release tracks (`main`, `preview`, `nightly`, `debug`).
- Releases: published and draft releases, staged rollout controls.
- Builds: uploaded build artifacts and their metadata.
- Shares: every public share link with stats, renewal, revocation, and passwords.
- Feedback: tickets submitted from the app, with assignees, statuses, and comments.
- Access: direct app members, deploy tokens, and Raft server visibility.
- Audit: recent app activity.
- Settings: app name/description, app icon, client key, default channel,
  public version history toggle, archive. Archived apps can be **purged**
  (permanent delete of the app, its builds/releases/tickets, and all stored
  files — requires typing the slug to confirm; `POST /api/apps/:id/purge`).

Create one Hands app per product distribution target. For example, an Android app should have its own Hands app and release channels.

## Releases

The standard flow follows the draft-first policy: CI creates a **draft** release with a generated changelog; a human or agent reviews it, writes the final (optionally bilingual) changelog, and publishes explicitly. See the release runbook in the repository docs.

1. CI (or `hands builds publish-android --draft`) creates the draft.
2. Review it in Releases (or `hands releases show`).
3. Write the final changelog — per-language notes are supported (`hands releases update … --changelog-file zh=zh.md --changelog-file en=en.md`); clients receive the language matching their system locale.
4. Publish from the release row (or `hands releases publish`).

For Android, Hands selects updates by `version_code`. A device receives an update only when the published release has a higher `version_code` than the client reports.

A draft, active, or superseded release reserves its app, channel, product type,
release type, and version-code coordinate. Cancelling disables that lifecycle
and releases the coordinate for a corrected upload while preserving the old
release ID, build, assets, and audit history. An old cancelled release can be
restored only until a replacement owns the version; after that, operate on the
replacement release instead. For Android builds that were already distributed,
use a higher version code so installed clients can receive the correction.

### Staged rollouts

Set a rollout percentage when creating or editing a full release (number input
with 5/25/50/100 presets), and raise it with **Bump rollout**. Select any number
of **Always included device groups** on that same release for acceptance or QA
devices. Group members receive the target regardless of their bucket; other
devices keep a stable bucket while the percentage climbs. Gated-out and
anonymous clients receive the previous eligible full release until rollout
reaches 100%.

Each release row shows unique per-install devices (UV): how many devices have
checked while already **on this version** and how many unique older devices
were **offered** it. Hover either number to see the all-time update-check event
count (PV). UV collection requires a stable device id and starts at the metric
upgrade; historical PV aggregates cannot be converted into exact historical UV.

The app overview also exposes version-level metrics from
`GET /api/apps/:id/analytics/versions`: devices that reported in the selected
window, total devices seen, update-check current/offered counts,
feedback/crash volume, and artifact downloads for each version/channel. The
data comes from throttled SDK metrics pings, update checks, feedback tickets,
and build asset download counters. These metrics show active/recent devices,
not a true unthrottled online heartbeat.

## Builds

Builds hold uploaded artifacts and provenance. Hands distinguishes installable artifacts from support artifacts:

| Artifact kind | Purpose |
|---|---|
| `installable` | APK or other file a client can install or download as the release payload. |
| `proguard-mapping` | Android R8/ProGuard mapping file for crash symbolication. |
| `native-symbols` | Native debug symbols. |
| `metadata-file` | CI or build metadata archived with the build. |
| `app-icon` | Launcher icon extracted automatically from the uploaded APK. |

Registering an installable APK triggers automatic parsing: package id, SDK levels, and the launcher icon are extracted server-side (aapt) with no extra CI parameters. Public update checks only return installable artifacts; support artifacts remain available to authenticated admins and publishers.

## Channels

Channels let you separate release tracks such as `main`, `preview`, `nightly`, or `debug`. Publish test builds to a non-production channel first, then publish to the production channel after validation.

Android clients should send the channel they are configured to use. Debug builds can point at `debug`; release builds typically point at `main`.

## Shares

The Shares tab lists every public share link for the app: release, creator, expiry, status, password badge, and view/download stats. From there you can create links (with an optional password), extend them by 7 days, set or remove passwords on existing links, and revoke them. Share URLs are shown once at creation — tokens are stored hashed.

Share pages show the release's real app icon, a QR code on desktop, localized release notes, and live stats. Password-protected pages ask for the password before showing the download.

```bash
hands releases share raft-android <release-id> --password <pw>   # optional password
hands releases update-share raft-android <release-id> <share-id> --ttl-seconds 1209600
hands releases revoke-share raft-android <release-id> <share-id>
```

Use share pages for human review and manual testing. Use the public update API for in-app update checks.

## Feedback

The Feedback tab is a lightweight ticket system for reports submitted from the app (via the SDK's `HandsFeedback` or the public feedback endpoint). Each ticket carries the message, contact, app version, device context (including the rollout device id), and up to nine attachments.

- Tickets are shareable pages (`/apps/<id>/feedback/<ticket>`).
- Triage with statuses (`open → in_progress → resolved/closed`), an assignee (Assign to me / edit / unassign), and a comment trail.
- A `feedback:new` webhook fires on submission for org webhook subscribers.
- Crash alerting: `crash:new_group` fires the first time a crash signature is
  seen for an app; `crash:spike` fires as a signature crosses 10, 50, and 100
  occurrences within an hour. Subscribe a webhook to either event (Org
  settings → Webhooks) to get paged instead of polling the console.

Crash tickets (`kind=crash`, submitted automatically by the SDK's crash
reporter) get a grouping **signature** (exception class + top app frame) and a
**Crash groups** view that aggregates by signature with occurrence and device
counts. When a build's ProGuard/R8 `mapping.txt` was uploaded as a
`proguard-mapping` asset for that version, Hands auto-deobfuscates the stack
in the container and posts the retraced trace as a ticket comment.

## Version history

Settings → **Public version history** exposes `/apps/<slug>/history`: a public page listing published versions with localized changelogs and per-version downloads. It is off by default; when disabled the page returns 404.

## Access

The Access page controls who can see or publish an app.

| Access type | Use |
|---|---|
| Direct app member | Give one user or agent a role on this app. |
| Raft server visibility | Make the app visible to accounts from another Raft server. |
| Deploy token | Give CI or an agent scoped API access to this app. |

For a principal on another linked Raft server, ask them to run the Hands
`whoami` Agent Login action and send the non-secret Hands account id. In
**Add direct app member**, paste that id instead of selecting a same-org
principal. The account must have logged into Hands at least once. Hands adds
missing org `viewer` membership and the selected app-scoped role atomically.

Deploy tokens are app-scoped bearer tokens. Create them for automation instead of reusing a human browser session. Copy the token when it is created; Hands only shows the raw token once. Each token records who created it, and actions performed with it are attributed as `deploy-token:<name>@<app>` in audit logs and release provenance.

Roles range from `viewer` (read) through `member` (collaborate — e.g. triage feedback), `publisher` (ship builds/releases), to `admin` (configure, manage members, hold secrets). See the [permissions reference](permissions.md) for the full role model and the endpoint→role matrix.

## Common Issues

### No update is found

Check that the published release is on the same channel as the client, has a higher `version_code`, contains a compatible installable artifact, and — for partially rolled-out releases — that the device's bucket falls inside the rollout percentage.

### Download link is expired

Public artifact URLs are signed and time-limited. Refresh the share page or run the update check again to get a fresh URL.

### 403 forbidden

Your account or deploy token does not have the required role for the action. The response is machine-readable so an agent or the CLI can point you straight to the fix:

```json
{
  "error": "insufficient_org_role",       // or "insufficient_app_role"
  "required_role": "member",
  "current_role": "viewer",
  "resource": "POST /api/apps",            // the action you attempted
  "org_id": "…", "app_id": null,
  "manage_url": "https://app.hands.build/orgs/{orgId}/members"
}
```

To resolve, an organization **admin/owner** opens `manage_url` — **Org → Members** for org roles, or an app's **Access** tab for app-level roles — and raises the account's role to `required_role`. Then retry the same request.

Note: creating an app (`POST /api/apps`) requires an **org member or higher** — an app-level member role or a deploy token is not enough. Newly Agent-Login'd agents normally start as **member**; **viewer** is a read-only role that an org admin can assign manually.


## Connect Google Play without a service-account key

See the [Google Play authorization setup guide](google-play-oauth.md) for the complete setup and troubleshooting steps.

For an Android app, open Settings and find Google Play. Enter the package name
and the internal, closed testing and production track names, then choose
**Authorize with Google**. Sign in with the Google account that has Play Console
access to that package and those tracks. Only a human Hands app administrator
can authorize. Connection testing checks access without publishing a release.

Hands encrypts the authorization on the server and refreshes access when needed.
If the Google button is unavailable, your Hands operator must configure the
Google OAuth client and private Play adapter first. Existing service-account
JSON connections are still supported.

**Unbind** removes the authorization stored by Hands for this app. To revoke
Google's grant too, follow the Google Account connections link and remove Hands;
this can affect other apps using that Google account. Play publication still
requires a valid signed AAB and the existing acceptance and approval steps.
