HideMyAndroid Headless API

Control HideMyAndroid entirely from ADB or any automation tool — create, configure, and activate profiles without opening the app. Premium, app 1.2.8+.

Last updated: August 2026

Requirements

The Headless API lets you control HideMyAndroid entirely from ADB or any automation tool — no app UI needed. It is a Premium feature.

  • Premium account — the Headless API is Premium-only.
  • App version 1.2.8 or newer. Everything added since is an extra field or method, so 1.2.8 still works — but the newer pieces need a newer build: hook_spoof_uptime needs 1.3.4+, geo and profile.duplicate need 1.4.5+, and the whole backup / restore group needs 1.5.3+. An older build ignores a field it does not know and still answers ok=true — but an unknown method is louder: calling the backup / restore group on a build before 1.5.3 returns BAD_REQUEST with error_message “Unknown method: …”. If a setting seems to do nothing, check the running version with status — a packaged release reports it faithfully, while a self-compiled build made before the version bump can carry a feature yet still report the older number.
  • Root + LSPosed — the same setup HideMyAndroid already needs. See the installation guide.
  • ADB connected to your device (adb devices lists it).
  • Internet on the device — needed only when you enable a proxy (it is tested live).

⚠️ VPN permission is only needed for profiles that use a proxy. Headless cannot raise the system VPN consent dialog, so grant it once from the app's Headless API screen. Without it, activating a proxy profile still activates the profile — but the proxy will not connect and the call returns INTERNAL saying exactly that.

Authentication

Open Settings → Developer → Headless API in the app and turn on Enable. A key is generated automatically — tap Copy. Every request sends this key as the token parameter.

Keep it private — anyone with the key and ADB access can control your profiles. Regenerate issues a new key (the old one stops working); logging out erases it.

Throughout this reference, replace <your_access_token> with your key and <profile_id> with the id returned by profile.create.

Making requests

Two transports, chosen by whether a value contains a colon :

Transport Use for Command
content call Reads, and values without : (name, gmails, package names)adb shell content call
am broadcast JSON objects (hookFlags, proxy, deviceMock, sim) — they contain :adb shell am broadcast

content call splits each --extra on the colon, so it cannot carry JSON — that is why JSON values go through the broadcast instead.

Only three methods have a broadcast form at all: HEADLESS_UPDATE (profile.update), HEADLESS_SET_ACTIVE and HEADLESS_DEACTIVATE. The other fifteen — status, every read, profile.create, profile.duplicate, profile.delete, the target-app calls and the whole backup / restore group — are content call only. There is no HEADLESS_CREATE — or HEADLESS_BACKUP — to go looking for.

The broadcast is not JSON-only. HEADLESS_UPDATE forwards exactly seven extras — name, hookFlags, proxy, geo, deviceMock, sim, gmails — so name and gmails travel over either transport. The table above is about which one is convenient, not which one is possible. Any extra whose name is not on that list of seven is dropped without an error.

Responses. Success is ok=true (content call) or result=0 (broadcast); failures add error_code and error_message. See Error codes. Every method also takes token. Click any operation below to expand its parameters, request, and response.

Status

READ status Check the API is ready content call

Parameters

No parameters.

Request

bash
adb shell content call --uri content://com.wowsoftware.hidemyandroid.headless --method status --extra token:s:<your_access_token>

Response ok=true

Bundle[{data={"premium":true,"featureEnabled":true,"freshnessOk":true,"activeProfileId":"a1b2c3d4-...","version":"1.5.3"}, ok=true}]

Fields: premium, featureEnabled, freshnessOk, activeProfileId (or null), version. freshnessOk is true when the app completed an online license validation within the last 24 hours. The check itself is offline — a headless request never hits the network, it only reads the stored result, and premium is read offline too. Open the app at least once a day to keep freshnessOk true.

💡 status is the only method that still answers when the licence is expired or stale — every other call returns LICENSE_EXPIRED or LICENSE_STALE and nothing else. That makes it the way to tell those two apart when a request is refused: read premium to see whether Premium genuinely ran out, and freshnessOk to see whether the app merely needs opening once. status is still gated by FEATURE_DISABLED and UNAUTHORIZED, so a silent API or a bad token look the same here as anywhere else.

Profiles

READ profile.list List all profiles content call

Parameters

No parameters.

Request

bash
adb shell content call --uri content://com.wowsoftware.hidemyandroid.headless --method profile.list --extra token:s:<your_access_token>

Response ok=true

Bundle[{data=[{"id":"a1b2...","name":"Demo","isActive":true,"createdAt":1717000000000,"updatedAt":1717000500000}], ok=true}]
READ profile.get Read one profile in full content call

Parameters

Name Type Required Description
id string required The profile id.

Request

bash
adb shell content call --uri content://com.wowsoftware.hidemyandroid.headless --method profile.get --extra token:s:<your_access_token> --extra id:s:<profile_id>

Response ok=true

Bundle[{data={"id":"a1b2...","name":"Demo","isActive":false,"hookFlags":{"hook_wifi":true,...},"proxy":{"enabled":false,...},"geo":{"source":"PROXY",...},"deviceMock":{"enabled":false,...},"sim":{"enabled":false,"simCards":[]},"gmails":[],"targetApps":["com.whatsapp"]}, ok=true}]
CREATE profile.create Create a profile (returns its id) content call

Parameters

Name Type Required Description
name string required A label for the profile.

Request

bash
adb shell content call --uri content://com.wowsoftware.hidemyandroid.headless --method profile.create --extra token:s:<your_access_token> --extra name:s:Demo

Response ok=true

Bundle[{data={"id":"a1b2c3d4-..."}, ok=true}]

Copy this id in as <profile_id> for later calls.

CREATE profile.duplicate Clone a profile with a fresh identity content call

Parameters

Name Type Required Description
id string required The id of the profile to copy from.

Request

bash
adb shell content call --uri content://com.wowsoftware.hidemyandroid.headless --method profile.duplicate --extra token:s:<your_access_token> --extra id:s:<profile_id>

Response ok=true

Bundle[{data={"id":"e5f6a7b8-...","name":"Brave Falcon 42"}, ok=true}]

Both the new id and the generated name come back — profile.create returns only an id, so do not expect these two to match.

⚠️ The name is generated for you and cannot be chosen in the call. Rename the copy with profile.update straight after if you want your own.

What a duplicate carries over

Clones a profile the way the Duplicate menu does in the app — needs app 1.4.5+. Settings carry over, but every identifying value is regenerated, so the copy has its own fingerprint instead of being a twin of the original. There is no broadcast form: a fire-and-forget broadcast could not hand you the new id, which is the whole point of the call.

What In the copy
Target apps, hookFlags, proxy (including its validated geo), geo, hidden packages Carried over unchanged
Every device identifier, at profile level and for each app in the profile Reissued, so the copy carries its own fingerprint instead of being a twin of the original
deviceMock (when enabled) Rolled to a different device template
sim Same country, new numbers
Backup packages Not copied — backups are keyed to {package}-{profileId}, so the copy owns none and starts empty
Virtual Gmail accounts One fresh address if the source had hook_virtual_accounts on; an empty list if it did not
Name Generated and guaranteed unique, in the form Brave Falcon 42 — you cannot pick it, which is why it is returned to you
Active state Always isActive: false — duplicating never activates

To end up with a name of your own, duplicate first and then rename in a second call:

bash
# 1. duplicate — the new id and its generated name come back together
adb shell content call --uri content://com.wowsoftware.hidemyandroid.headless --method profile.duplicate --extra token:s:<your_access_token> --extra id:s:<source_profile_id>

# 2. rename the copy — profile.duplicate never takes a name of your own
adb shell content call --uri content://com.wowsoftware.hidemyandroid.headless --method profile.update --extra token:s:<your_access_token> --extra id:s:<new_profile_id> --extra name:s:"My Profile"
UPDATE profile.update Change a profile's settings content call / broadcast

Parameters

Name Type Required Description
id string required The profile id.
name string optional New profile label (content call).
gmails string[] optional Virtual @gmail.com accounts (content call).
hookFlags object optional Spoofing on/off switches (broadcast).
proxy object optional Proxy config (broadcast).
geo object optional Location source — proxy or custom (broadcast). Needs app 1.4.5+.
deviceMock object optional Device model spoof (broadcast).
sim object optional SIM / carrier spoof (broadcast).

Request

content call — plain values

bash
adb shell content call --uri content://com.wowsoftware.hidemyandroid.headless --method profile.update --extra token:s:<your_access_token> --extra id:s:<profile_id> --extra name:s:"My Profile"

broadcast — JSON configs

bash
adb shell am broadcast -a com.wowsoftware.hidemyandroid.HEADLESS_UPDATE -p com.wowsoftware.hidemyandroid -e token <your_access_token> -e id <profile_id> -e hookFlags '{"hook_wifi":false,"hook_nearby_bluetooth":false}'

Response ok=true

Both forms return the full updated profile. See Configuration for every JSON field.

⚠️ hookFlags replaces the whole set — any flag you omit reverts to its default, which is ON for every flag except hook_spoof_uptime, where it is OFF. So an update that leaves out hook_spoof_uptime silently switches it back off. To change a few and keep the rest, run profile.get first, edit the full map, then send it all back.

DELETE profile.delete Delete a profile permanently content call

Parameters

Name Type Required Description
id string required The profile id.

Request

bash
adb shell content call --uri content://com.wowsoftware.hidemyandroid.headless --method profile.delete --extra token:s:<your_access_token> --extra id:s:<profile_id>

Response ok=true

Bundle[{data={}, ok=true}]

⚠️ Deleting a profile also deletes its app-data backups under /sdcard/HideMyAndroid_Backups/ — copy them off the device first if you need them. And since 1.5.3, profile.delete answers BUSY while a backup / restore job still holds the slot — even one already cancelled; retry until the BUSY stops.

Activation

A profile only spoofs once active, and only one profile is ever active. profile.setActive switches the active profile over — whichever profile was active before is deactivated for you, so there is no need to call profile.deactivate first.

⚠️ Activating clears the target apps' data. profile.setActive does not just restart them: it force-stops each target app and wipes its data, so it comes back up clean and actually picks up the new identity. That is deliberate — but it means anything inside those apps is gone, logged-in sessions included, In the app the same action asks you to confirm first and points you at the Backup feature; a headless call raises no dialog and runs straight through, so this warning is the only one you get. Headlessly, the safety net is backup.start (1.5.3+) before you switch. And since 1.5.3, while a backup / restore job is still holding the slot, profile.setActive and profile.deactivate answer BUSY — even for a job already cancelled; retry until the BUSY stops.

ACTION profile.setActive Activate a profile — start spoofing content call / broadcast

Parameters

Name Type Required Description
id string required The profile id. Over the broadcast it is accepted as either id or profile_id.

Request

content call — recommended

bash
adb shell content call --uri content://com.wowsoftware.hidemyandroid.headless --method profile.setActive --extra token:s:<your_access_token> --extra id:s:<profile_id>

broadcast — for automation

bash
adb shell am broadcast -a com.wowsoftware.hidemyandroid.HEADLESS_SET_ACTIVE -p com.wowsoftware.hidemyandroid -e token <your_access_token> -e id <profile_id>

Response ok=true

Bundle[{data={"id":"a1b2...","isActive":true}, ok=true}]
ACTION profile.deactivate Deactivate the active profile — stop spoofing content call / broadcast

Parameters

No parameters.

Request

content call — recommended

bash
adb shell content call --uri content://com.wowsoftware.hidemyandroid.headless --method profile.deactivate --extra token:s:<your_access_token>

broadcast — for automation

bash
adb shell am broadcast -a com.wowsoftware.hidemyandroid.HEADLESS_DEACTIVATE -p com.wowsoftware.hidemyandroid -e token <your_access_token>

Response ok=true

Bundle[{data={}, ok=true}]

No id needed — it deactivates whichever profile is active.

content call is the form to prefer: it is synchronous and prints the returned Bundle. The HEADLESS_SET_ACTIVE and HEADLESS_DEACTIVATE broadcasts do the same job from automation tools such as Tasker. They reply as an ordered broadcast, which always carries data back: on success am broadcast prints result=0 with the result JSON in data="…", and on failure result=1 with data="{"error_code":…,"error_message":…}". So they are not strictly fire-and-forget — parse data= and you get the same answer content call would have given. HEADLESS_SET_ACTIVE takes the profile id as either -e id or -e profile_id; HEADLESS_DEACTIVATE needs no id at all. As always, include -p com.wowsoftware.hidemyandroid.

Target apps

Target apps are the apps a profile spoofs.

READ profile.getApps List a profile's target apps content call

Parameters

Name Type Required Description
id string required The profile id.

Request

bash
adb shell content call --uri content://com.wowsoftware.hidemyandroid.headless --method profile.getApps --extra token:s:<your_access_token> --extra id:s:<profile_id>

Response ok=true

Bundle[{data=[{"packageName":"com.whatsapp"}], ok=true}]
CREATE profile.addApp Add a target app to a profile content call

Parameters

Name Type Required Description
id string required The profile id.
packageName string required The app's package name.

Request

bash
adb shell content call --uri content://com.wowsoftware.hidemyandroid.headless --method profile.addApp --extra token:s:<your_access_token> --extra id:s:<profile_id> --extra packageName:s:com.whatsapp

Response ok=true · updated app list

Bundle[{data=[{"packageName":"com.whatsapp"}], ok=true}]

⚠️ Adding the same package twice returns BAD_REQUEST.

DELETE profile.removeApp Remove a target app from a profile content call

Parameters

Name Type Required Description
id string required The profile id.
packageName string required The app to remove.

Request

bash
adb shell content call --uri content://com.wowsoftware.hidemyandroid.headless --method profile.removeApp --extra token:s:<your_access_token> --extra id:s:<profile_id> --extra packageName:s:com.whatsapp

Response ok=true · updated app list

Bundle[{data=[], ok=true}]

⚠️ Removing a package that is not in the profile still succeeds and changes nothing — it is not an error. That is deliberately unlike addApp, where adding a duplicate returns BAD_REQUEST.

Backup & restore

New in 1.5.3, this group copies a profile's app data into archives on the device and pours it back later — the same job as Manage Backups in the app, minus the UI. All six methods are content call only (no broadcast form), and the two *.start calls are asynchronous: they hand you a jobId immediately and keep working in the background — poll backup.job to follow along. Backing up a single app can take minutes.

⚠️ One job at a time. A second *.start while one runs returns BUSY, with the running jobId in error_message. The two *.start calls also require the target profile to be active — otherwise REQUIRES_ACTIVE_PROFILE, and there is no override flag. The other four methods work on any profile. And as everywhere in the headless API, nothing asks for confirmation — every call runs straight through.

ACTION backup.start Start a backup job for the active profile content call

Parameters

Name Type Required Description
id string required The profile id — must be the currently active profile.
packages string optional Comma-separated package names to back up (no spaces). Omit to back up every target app of the profile. Each package must belong to the profile.

Request

content call — every target app

bash
adb shell content call --uri content://com.wowsoftware.hidemyandroid.headless --method backup.start --extra token:s:<your_access_token> --extra id:s:<profile_id>

content call — selected packages only

bash
adb shell content call --uri content://com.wowsoftware.hidemyandroid.headless --method backup.start --extra token:s:<your_access_token> --extra id:s:<profile_id> --extra packages:s:com.twitter.android,com.facebook.katana

Response ok=true · job accepted

Bundle[{data={"jobId":"f0e1d2c3-...","type":"backup","total":2,"packages":["com.twitter.android","com.facebook.katana"],"foregroundHeld":true}, ok=true}]

The call returns the moment the job is accepted — it does not wait for the copy. total is how many apps the job will process; poll backup.job to follow it. For what foregroundHeld does (and does not) promise, see the notes below.

⚠️ A backup never force-stops or clears the app. To read a consistent snapshot it briefly pauses the app (SIGSTOP) while archiving each component, then resumes it (SIGCONT) — the app keeps running and loses nothing. Backing up a package that already has a backup replaces the old archive: one backup per app per profile, no history.

ACTION restore.start Restore backed-up app data into the active profile content call

Parameters

Name Type Required Description
id string required The profile id — must be the currently active profile. A backup only restores into the profile that made it.
packages string optional Comma-separated package names to restore (no spaces). Omit to restore every app that has a backup in this profile — apps without one are silently skipped. Name packages explicitly and it is fail-fast: one missing backup fails the whole call with NO_BACKUP and nothing is restored.

Request

bash
adb shell content call --uri content://com.wowsoftware.hidemyandroid.headless --method restore.start --extra token:s:<your_access_token> --extra id:s:<profile_id>

Response ok=true · job accepted

Bundle[{data={"jobId":"09a8b7c6-...","type":"restore","total":1,"packages":["com.twitter.android"],"foregroundHeld":true}, ok=true}]

Same job model as backup.start — the response is immediate and the type field says "restore". Poll backup.job to follow it; there is no separate restore.job.

⚠️ Restore force-stops the target app (am force-stop) and does not restart it, then extracts the archive over /data/data/<pkg> without clearing the directory first — files the app created after the backup survive it. And backups are keyed to package + profile, which is why cross-profile restore does not exist — you cannot pour one profile's backup into another.

READ backup.job Poll a job's progress and results content call

Parameters

Name Type Required Description
jobId string optional The job to inspect. Omit it to read the most recent job.

Request

content call — a specific job

bash
adb shell content call --uri content://com.wowsoftware.hidemyandroid.headless --method backup.job --extra token:s:<your_access_token> --extra jobId:s:<job_id>

content call — the latest job (no jobId)

bash
adb shell content call --uri content://com.wowsoftware.hidemyandroid.headless --method backup.job --extra token:s:<your_access_token>

Response ok=true

# while it runs
Bundle[{data={"jobId":"f0e1d2c3-...","type":"backup","profileId":"a1b2...","state":"RUNNING","total":2,"done":0,"ok":0,"failed":0,"currentPackage":"com.twitter.android","currentComponent":"Internal Data","progress":0.31,"results":[],"error":null,"foregroundHeld":true,"startedAt":1712345678901,"finishedAt":null}, ok=true}]

# when it finishes — note: DONE even though one app failed
Bundle[{data={"jobId":"f0e1d2c3-...","type":"backup","profileId":"a1b2...","state":"DONE","total":2,"done":2,"ok":1,"failed":1,"currentPackage":null,"currentComponent":null,"progress":1.0,"results":[{"packageName":"com.twitter.android","ok":true,"error":null},{"packageName":"com.facebook.katana","ok":false,"error":"tar exited 2"}],"error":null,"foregroundHeld":true,"startedAt":1712345678901,"finishedAt":1712345699999}, ok=true}]

One method serves both job types — type says "backup" or "restore". state is RUNNING, DONE, FAILED or CANCELLED; progress runs 0..1 across the whole job; startedAt / finishedAt are epoch milliseconds and finishedAt stays null while the job runs. DONE means the job ran to the end — not that every app succeeded — so always read failed and results[] (one {packageName, ok, error} per app). currentComponent is a human-readable stage name ("Internal Data", "Device Encrypted Data", "External Data", "Media", "OBB", "Permissions", "SSAID") — deliberately not the same strings as components[] in backup.list. Works on any profile. Only the five most recent jobs are kept — an unknown or older jobId returns NOT_FOUND, and so does polling before any job has ever run.

ACTION backup.cancel Cancel a running job content call

Parameters

Name Type Required Description
jobId string optional The job to cancel. Omit it to cancel the most recent job.

Request

bash
adb shell content call --uri content://com.wowsoftware.hidemyandroid.headless --method backup.cancel --extra token:s:<your_access_token> --extra jobId:s:<job_id>

Response ok=true · job snapshot

Returns the job snapshot after cancellation — the same shape backup.job returns.

⚠️ Cancelling mid-app leaves that app's archive half-written: the app is not added to the backup list and the leftover files are in an undefined state. Do not restore from it — backup.delete the package and back it up again. And the snapshot flips to CANCELLED (with finishedAt set) immediately, but the root tar underneath cannot be interrupted mid-file and keeps running — so the job can still hold the slot after CANCELLED already shows (see the notes below).

READ backup.list List a profile's stored backups content call

Parameters

Name Type Required Description
id string required The profile id — any profile, not just the active one.

Request

bash
adb shell content call --uri content://com.wowsoftware.hidemyandroid.headless --method backup.list --extra token:s:<your_access_token> --extra id:s:<profile_id>

Response ok=true

Bundle[{data=[{"packageName":"com.twitter.android","appName":"X","timestamp":1712345678901,"totalSize":12345678,"components":["INTERNAL_DATA","EXTERNAL_DATA","OBB"]}], ok=true}]

One entry per backed-up app. appName is the display name captured at backup time (read from the archive's metadata.json, not derived from the package). timestamp is epoch milliseconds, totalSize is bytes, and components[] uses enum names: INTERNAL_DATA, DEVICE_ENCRYPTED, EXTERNAL_DATA, MEDIA, OBB, PERMISSIONS, SSAID.

DELETE backup.delete Delete one app's backup from a profile content call

Parameters

Name Type Required Description
id string required The profile id.
packageName string required The app whose backup to delete.

Request

bash
adb shell content call --uri content://com.wowsoftware.hidemyandroid.headless --method backup.delete --extra token:s:<your_access_token> --extra id:s:<profile_id> --extra packageName:s:com.twitter.android

Response ok=true

Bundle[{data={}, ok=true}]

Synchronous — no job is created; the backup folder is removed on the spot. Deleting a backup that is not there is not an error: the call is idempotent and still returns ok=true with data={}. NOT_FOUND only means the profile id does not exist; a malformed packageName returns BAD_REQUEST.

How jobs behave

  • DONE does not mean every app succeeded. It only means the job ran to the end. An app can fail while the job still finishes DONE — read failed and results[], always.
  • Activation waits for jobs now. While a job holds the slot, profile.setActive, profile.deactivate and profile.delete answer BUSY — even for a job you already cancelled, because the root tar underneath cannot be stopped mid-file, and no snapshot field reports the slot. A job that ends naturally in DONE or FAILED frees the slot almost at once; after a cancel, do not treat CANCELLED as “slot free” — just retry the call until BUSY stops.
  • foregroundHeld is an observation, not a promise. It reports whether a foreground service is shielding the job from being killed by the OS. It has been measured true on API 36, but even true only means the service start was accepted — the later promotion step can still fail silently on some devices. For long jobs, keep the device awake rather than lean on it.
  • Where backups live. /sdcard/HideMyAndroid_Backups/<packageName>-<profileId>/ — one folder per app per profile, holding .tar.gz archives (data.tar.gz, data_ext.tar.gz, media.tar.gz, …) plus metadata.json, written with root tar. The {package}-{profileId} key is what makes restore same-profile only. Backups made headlessly show up in the app's Manage Backups screen too.

Configuration

These are the values you send with profile.update. Defaults when a profile is created:

Setting Default To use it…
hookFlags (all 21) ✅ ON — except hook_spoof_uptime ❌ OFF Already on. Send false only for the ones to turn off — and true for hook_spoof_uptime, the one flag that starts off.
proxy ❌ OFF Include "enabled":true
geo PROXY Send "source":"CUSTOM" with coordinates, countryCode and timezone to set a location without a proxy
deviceMock ❌ OFF Include "enabled":true
sim ❌ OFF Include "enabled":true
gmails empty Send the addresses (virtual accounts on by default)

⚠️ A config with enabled:false is stored but inert — values are saved so you can flip it on later, but nothing is spoofed until enabled:true. geo is the exception: it has no enabled switch. The hook_geo_* flags apply whenever geo resolves to a usable location — either from source: CUSTOM, or from a proxy that is enabled:true and passed its live test.

hookFlags

Default: ON for every flag except hook_spoof_uptime, which is OFF. A map of "flag": true|false. An update replaces the whole map, but only the map you actually send: an update that omits the hookFlags extra altogether leaves every flag exactly as it was. Within a map you do send, every flag you leave out returns to its own default, so the other 20 go back ON while hook_spoof_uptime goes back OFF even if you had turned it on. To change a few and keep the rest, run profile.get first, edit the full map, then send it all back.

Flag What it does
hook_hide_dev_opts Hide Developer Options
hook_hide_vpn Hide VPN
hook_hide_airplane_mode Hide airplane mode
hook_hide_proxy Hide the system proxy setting
hook_hide_root Hide root
hook_hide_lsposed Hide LSPosed
hook_spoof_installer Spoof the install source
hook_package_info Spoof package info / signatures
hook_lan_scan_block Block LAN scanning
hook_identifiers_partly Spoof the basic device identifiers
hook_identifiers_fully Spoof the full set of device identifiers — includes everything hook_identifiers_partly covers
hook_wifi Spoof the connected Wi-Fi network's details
hook_nearby_wifi Spoof nearby Wi-Fi scan results
hook_nearby_bluetooth Spoof nearby Bluetooth devices
hook_realistic_sensor Use realistic sensor data
hook_sensor_accelerometer Spoof the accelerometer (needs hook_realistic_sensor)
hook_spoof_uptime Spoof device uptime — makes the device look like it has been running longer. Premium, experimental. Off by default — the only flag that is. Needs app 1.3.4+.
hook_virtual_accounts Enable virtual Google accounts (gmails need this ON)
hook_geo_gps Spoof GPS location (needs geo — proxy or custom)
hook_geo_locale Spoof language/region (needs geo — proxy or custom)
hook_geo_timezone Spoof timezone (needs geo — proxy or custom)

proxy

Default: OFF. With enabled:true, the app connects through it for real, reads its location, and fills countryCode/lat/lon/timezone. The live test takes up to ~8s; a proxy that fails returns PROXY_INVALID and is not saved. Those four are outputs — the app writes them, so anything you send in them is overwritten. To set a location by hand, use geo with source: CUSTOM instead; that needs no proxy at all. profile.get returns the proxy including those four fields, but never echoes username or password.

Field Type Default Meaning / constraints
host string "" Proxy IP / hostname
port int 0 Port — 165535, checked only when you enable the proxy
protocol string HTTP HTTP, SOCKS4, or SOCKS5
username string "" Auth (optional) — never returned by profile.get
password string "" Auth (optional) — never returned by profile.get
countryCode string "" Output — 2-letter country, filled from the live test
lat double 0 Output — filled from the live test
lon double 0 Output — filled from the live test
timezone string "" Output — IANA name, filled from the live test
bash
adb shell am broadcast -a com.wowsoftware.hidemyandroid.HEADLESS_UPDATE -p com.wowsoftware.hidemyandroid -e token <your_access_token> -e id <profile_id> -e proxy '{"enabled":true,"host":"10.0.0.1","port":1080,"protocol":"SOCKS5"}'

geo

Default: {"source":"PROXY"}. Needs app 1.4.5+. This is where a profile's location comes from, and there are two sources — pick one with source. PROXY uses the location read during the proxy's live test. CUSTOM lets you set the coordinates, timezone and region yourself, with no proxy required — so you can put a profile in Tokyo while routing traffic anywhere, or nowhere. The three hook_geo_* flags then switch each part on independently: GPS, region + language, and timezone.

⚠️ Mind the field names: geo spells them latitude/longitude, while proxy above uses the short lat/lon. They are two separate objects, not aliases — the short names in a geo payload are simply ignored.

Field Type Default Meaning / constraints
source string PROXY Required. Exactly PROXY or CUSTOM — a missing or misspelled value returns BAD_REQUEST, it is never quietly read as PROXY
latitude double 0 Required for CUSTOM — must be between -90 and 90; 0/0 counts as unset
longitude double 0 Required for CUSTOM — must be between -180 and 180; 0/0 counts as unset
countryCode string "" Required for CUSTOM — 2 letters, e.g. JP. Case-insensitive going in, always returned uppercase
timezone string "" Required for CUSTOM — IANA name, e.g. Asia/Tokyo. Only checked for being non-empty — see the warning below
language string "" Optional — 2-letter ISO 639-1 such as ja, not a full tag like ja-JP. Derived from countryCode when left empty, and always stored lowercase. Only CUSTOM can set it explicitly; on the PROXY path it is always derived

⚠️ source: CUSTOM must arrive complete: a coordinate pair that is not 0/0 and in range, a 2-letter countryCode, and a timezone. Miss any one and the call returns BAD_REQUEST — it does not fall back to the proxy. The app never geocodes and never goes online to fill these in for you. If neither source ends up usable, no geo hook is loaded at all and the real location shows through.

🛑 timezone is the one field that is not really checked. It is only tested for being non-empty — never against the IANA database. A typo like Asia/Tokyoo is accepted, returns ok=true, is echoed back unchanged by profile.get, and then silently resolves to GMT on the device. Nothing anywhere reports an error, so copy the name exactly. Coordinates are the opposite — they are range-checked and a bad one fails loudly with BAD_REQUEST.

CUSTOM — set a location by hand, no proxy needed

bash
adb shell am broadcast -a com.wowsoftware.hidemyandroid.HEADLESS_UPDATE -p com.wowsoftware.hidemyandroid -e token <your_access_token> -e id <profile_id> -e geo '{"source":"CUSTOM","latitude":35.6812,"longitude":139.7671,"countryCode":"JP","timezone":"Asia/Tokyo","language":"ja"}'

PROXY — take the location from the proxy (default)

bash
adb shell am broadcast -a com.wowsoftware.hidemyandroid.HEADLESS_UPDATE -p com.wowsoftware.hidemyandroid -e token <your_access_token> -e id <profile_id> -e geo '{"source":"PROXY"}'

deviceMock

Default: OFF. Pretends the device is a different model. Set "enabled":true plus any of these fields — all optional strings (default empty), each mapping to an Android Build property:

Field Maps to Example
manufacturer Build.MANUFACTURER Samsung
brand Build.BRAND samsung
model Build.MODEL SM-S918B
device Build.DEVICE (codename) dm3q
product Build.PRODUCT dm3qxxx
board Build.BOARD kalama
hardware Build.HARDWARE qcom
buildId Build.ID UP1A.231005.007
buildIncremental Build.VERSION.INCREMENTAL S918BXXU3CWK9
buildType Build.TYPE user
buildTags Build.TAGS release-keys
buildFingerprint Build.FINGERPRINT samsung/dm3qxxx/dm3q:14/UP1A.231005.007/S918BXXU3CWK9:user/release-keys
deviceName Displayed device name Galaxy S23 Ultra
bash
adb shell am broadcast -a com.wowsoftware.hidemyandroid.HEADLESS_UPDATE -p com.wowsoftware.hidemyandroid -e token <your_access_token> -e id <profile_id> -e deviceMock '{"enabled":true,"manufacturer":"Samsung","brand":"samsung","model":"SM-S918B","device":"dm3q","product":"dm3qxxx","board":"kalama","hardware":"qcom","buildId":"UP1A.231005.007","buildIncremental":"S918BXXU3CWK9","buildType":"user","buildTags":"release-keys","buildFingerprint":"samsung/dm3qxxx/dm3q:14/UP1A.231005.007/S918BXXU3CWK9:user/release-keys","deviceName":"Galaxy S23 Ultra"}'

sim

Default: OFF. Spoofs the SIM/carrier. Set "enabled":true and provide up to two cards in simCards. The list is always normalised to exactly two: a third card is dropped silently — the call still returns ok=true — and a single card is padded with an empty second one, which is why profile.get always hands back two. Each card:

Field Type Default Meaning / constraints
slotIndex int 0 SIM slot — assigned from the card's position in the array (0, then 1); the value you send is ignored
carrierName string "" Carrier name (e.g. AT&T)
countryIso string "" ISO country code (e.g. us) — stored lowercase. Note this runs opposite to geo.countryCode, which is stored uppercase
mcc string "" Mobile Country Code (e.g. 310)
mnc string "" Mobile Network Code (e.g. 410)
operatorCode string "" Operator code = mcc + mnc (e.g. 310410)
phoneNumber string "" Phone number
iccid string "" SIM serial number (ICCID)
imsi string "" IMSI
networkType string "" Network type — see the accepted values below; an unrecognised one is ignored in silence

networkType is trimmed and matched case-insensitively, and each tier answers to more than one spelling:

Value Also accepted Network tier
NR 5G NR, 5G 5G
LTE 4G 4G
WCDMA UMTS, 3G 3G
GSM 2G 2G

⚠️ Anything outside this table silently disables the network-type spoof for that card — no error, no BAD_REQUEST, the value is simply stored and never used. Note that NR 5G carries a space: NR5G is not recognised and falls into exactly that silent hole.

bash
adb shell am broadcast -a com.wowsoftware.hidemyandroid.HEADLESS_UPDATE -p com.wowsoftware.hidemyandroid -e token <your_access_token> -e id <profile_id> -e sim '{"enabled":true,"simCards":[{"slotIndex":0,"carrierName":"AT&T","countryIso":"us","mcc":"310","mnc":"410","operatorCode":"310410","networkType":"LTE"},{"slotIndex":1,"carrierName":"T-Mobile","countryIso":"us","mcc":"310","mnc":"260","operatorCode":"310260","networkType":"NR 5G"}]}'

gmails

Default: empty. A list of virtual Google accounts — only @gmail.com addresses are accepted. The list replaces the previous one; [] clears it. No : inside, so content call works:

bash
adb shell content call --uri content://com.wowsoftware.hidemyandroid.headless --method profile.update --extra token:s:<your_access_token> --extra id:s:<profile_id> --extra 'gmails:s:["alice@gmail.com","bob@gmail.com"]'

Full example

A complete setup from scratch:

bash
# 1. create a profile — copy the returned id into <profile_id> below
adb shell content call --uri content://com.wowsoftware.hidemyandroid.headless --method profile.create --extra token:s:<your_access_token> --extra name:s:Demo

# 2. add a target app
adb shell content call --uri content://com.wowsoftware.hidemyandroid.headless --method profile.addApp --extra token:s:<your_access_token> --extra id:s:<profile_id> --extra packageName:s:com.whatsapp
# 3. add the virtual Gmail accounts
adb shell content call --uri content://com.wowsoftware.hidemyandroid.headless --method profile.update --extra token:s:<your_access_token> --extra id:s:<profile_id> --extra 'gmails:s:["alice@gmail.com","bob@gmail.com"]'

# 4. enable + set the device mock
adb shell am broadcast -a com.wowsoftware.hidemyandroid.HEADLESS_UPDATE -p com.wowsoftware.hidemyandroid -e token <your_access_token> -e id <profile_id> -e deviceMock '{"enabled":true,"manufacturer":"Samsung","brand":"samsung","model":"SM-S918B","device":"dm3q","product":"dm3qxxx","board":"kalama","hardware":"qcom","buildId":"UP1A.231005.007","buildIncremental":"S918BXXU3CWK9","buildType":"user","buildTags":"release-keys","buildFingerprint":"samsung/dm3qxxx/dm3q:14/UP1A.231005.007/S918BXXU3CWK9:user/release-keys","deviceName":"Galaxy S23 Ultra"}'

# 5. enable + set the SIM mock (two cards, full fields)
adb shell am broadcast -a com.wowsoftware.hidemyandroid.HEADLESS_UPDATE -p com.wowsoftware.hidemyandroid -e token <your_access_token> -e id <profile_id> -e sim '{"enabled":true,"simCards":[{"slotIndex":0,"carrierName":"AT&T","countryIso":"us","mcc":"310","mnc":"410","operatorCode":"310410","networkType":"LTE","phoneNumber":"+12025550123","iccid":"8901410123456789012","imsi":"310410123456789"},{"slotIndex":1,"carrierName":"T-Mobile","countryIso":"us","mcc":"310","mnc":"260","operatorCode":"310260","networkType":"NR 5G","phoneNumber":"+12025550456","iccid":"8901260987654321098","imsi":"310260987654321"}]}'

# 6. enable + set the proxy (tested live, ~8s)
adb shell am broadcast -a com.wowsoftware.hidemyandroid.HEADLESS_UPDATE -p com.wowsoftware.hidemyandroid -e token <your_access_token> -e id <profile_id> -e proxy '{"enabled":true,"host":"10.0.0.1","port":1080,"protocol":"SOCKS5"}'

# 7. pick where the location comes from — geo has two sources, choose ONE
#    A) from the proxy you just set. This is the default, so the line is optional:
adb shell am broadcast -a com.wowsoftware.hidemyandroid.HEADLESS_UPDATE -p com.wowsoftware.hidemyandroid -e token <your_access_token> -e id <profile_id> -e geo '{"source":"PROXY"}'
#    B) or set it yourself — this needs no proxy at all. Use B *instead of* A, not after it:
# adb shell am broadcast -a com.wowsoftware.hidemyandroid.HEADLESS_UPDATE -p com.wowsoftware.hidemyandroid -e token <your_access_token> -e id <profile_id> -e geo '{"source":"CUSTOM","latitude":35.6812,"longitude":139.7671,"countryCode":"JP","timezone":"Asia/Tokyo","language":"ja"}'
#    geo and proxy are independent: setting one never rewrites the other, and only
#    geo.source decides which one feeds the hooks. So CUSTOM together with a proxy in
#    a different country means your IP says one place while your GPS says another.

# 8. activate (restarts the target app and clears its data)
adb shell content call --uri content://com.wowsoftware.hidemyandroid.headless --method profile.setActive --extra token:s:<your_access_token> --extra id:s:<profile_id>

# 9. verify
adb shell content call --uri content://com.wowsoftware.hidemyandroid.headless --method profile.get --extra token:s:<your_access_token> --extra id:s:<profile_id>

Error codes

On failure you get ok=false with one of these error_code values (broadcasts return result=1):

Code Meaning Fix
UNAUTHORIZED Token missing or wrong Use your current key; regenerate if unsure
FEATURE_DISABLED Headless API is off Enable it in Settings → Developer → Headless API
BAD_REQUEST Bad parameter or JSON Check required fields, valid JSON, gmail-only, no duplicate app, packages that belong to the profile
NOT_FOUND That profile, job or backup doesn't exist Confirm the id with profile.list, backup.job or backup.list
BUSY A backup / restore job is still holding the slot error_message names the running jobId — wait for the job to end, then retry; after a cancel the slot can outlive the CANCELLED state, so retry until BUSY stops. Also returned by profile.setActive / deactivate / delete while a job holds the slot
REQUIRES_ACTIVE_PROFILE backup.start / restore.start on a profile that is not active profile.setActive it first — there is no override flag
NO_BACKUP restore.start found no backup for a requested app (or the profile has none at all) Check backup.list; run backup.start first
PROXY_INVALID Proxy failed the live test Check host/port/protocol/auth and the device's internet; it was not saved
LICENSE_EXPIRED Premium expired Renew Premium
LICENSE_STALE License not checked online recently Open the app once to re-validate (at least every 24h for non-stop use)
INTERNAL Internal error (I/O or root) Retry; check that root/LSPosed are working

Tips & troubleshooting

  • A broadcast does nothing? Make sure you included -p com.wowsoftware.hidemyandroid — Android blocks broadcasts without it.
  • A value got cut off after a :? That value has a colon, so content call truncated it. Send it via the HEADLESS_UPDATE broadcast instead.
  • JSON broadcast not applying (often on Windows)? Across a desktop shell and the device shell, the quotes in am broadcast JSON (hookFlags, proxy, deviceMock, sim) are easily stripped, so am misreads the value (you may see it land as a dat=… data URI) and nothing changes. The reliable fix is to keep the JSON off the command line — put the whole workflow in a .sh file (single quotes keep every JSON intact), then push and run it on the device. The script runs on the device, so it can even capture the new profile id and reuse it:
    bash
    # setup.sh — the full example as one runnable script (runs on the device)
    URI=content://com.wowsoftware.hidemyandroid.headless
    PKG=com.wowsoftware.hidemyandroid
    K=<your_access_token>
    
    # 1. create a profile and capture its id
    ID=$(content call --uri $URI --method profile.create --extra token:s:$K --extra name:s:Demo | grep -o '"id":"[^"]*"' | head -n1 | sed 's/.*"id":"//;s/"//')
    echo "profile id: $ID"
    
    # 2. add a target app
    content call --uri $URI --method profile.addApp --extra token:s:$K --extra id:s:$ID --extra packageName:s:com.facebook.katana
    
    # 3. add the virtual Gmail accounts
    content call --uri $URI --method profile.update --extra token:s:$K --extra id:s:$ID --extra 'gmails:s:["alice@gmail.com","bob@gmail.com"]'
    
    # 4. enable + set the device mock
    am broadcast -a $PKG.HEADLESS_UPDATE -p $PKG -e token $K -e id $ID -e deviceMock '{"enabled":true,"manufacturer":"Samsung","brand":"samsung","model":"SM-S918B","device":"dm3q","product":"dm3qxxx","board":"kalama","hardware":"qcom","buildId":"UP1A.231005.007","buildIncremental":"S918BXXU3CWK9","buildType":"user","buildTags":"release-keys","buildFingerprint":"samsung/dm3qxxx/dm3q:14/UP1A.231005.007/S918BXXU3CWK9:user/release-keys","deviceName":"Galaxy S23 Ultra"}'
    
    # 5. enable + set the SIM mock (two cards)
    am broadcast -a $PKG.HEADLESS_UPDATE -p $PKG -e token $K -e id $ID -e sim '{"enabled":true,"simCards":[{"slotIndex":0,"carrierName":"AT&T","countryIso":"us","mcc":"310","mnc":"410","operatorCode":"310410","networkType":"LTE","phoneNumber":"+12025550123","iccid":"8901410123456789012","imsi":"310410123456789"},{"slotIndex":1,"carrierName":"T-Mobile","countryIso":"us","mcc":"310","mnc":"260","operatorCode":"310260","networkType":"NR 5G","phoneNumber":"+12025550456","iccid":"8901260987654321098","imsi":"310260987654321"}]}'
    
    # 6. enable + set the proxy (tested live, ~8s)
    am broadcast -a $PKG.HEADLESS_UPDATE -p $PKG -e token $K -e id $ID -e proxy '{"enabled":true,"host":"10.0.0.1","port":1080,"protocol":"SOCKS5"}'
    
    # 7. pick where the location comes from — geo has two sources, choose ONE
    #    A) from the proxy you just set. This is the default, so the line is optional:
    am broadcast -a $PKG.HEADLESS_UPDATE -p $PKG -e token $K -e id $ID -e geo '{"source":"PROXY"}'
    #    B) or set it yourself — this needs no proxy at all. Use B *instead of* A, not after it:
    # am broadcast -a $PKG.HEADLESS_UPDATE -p $PKG -e token $K -e id $ID -e geo '{"source":"CUSTOM","latitude":35.6812,"longitude":139.7671,"countryCode":"JP","timezone":"Asia/Tokyo","language":"ja"}'
    #    geo and proxy are independent: setting one never rewrites the other, and only
    #    geo.source decides which one feeds the hooks. So CUSTOM together with a proxy in
    #    a different country means your IP says one place while your GPS says another.
    
    # 8. activate (restarts the target app and clears its data)
    content call --uri $URI --method profile.setActive --extra token:s:$K --extra id:s:$ID
    
    # 9. verify
    content call --uri $URI --method profile.get --extra token:s:$K --extra id:s:$ID
    then push it to the device and run it:
    bash
    adb push setup.sh /data/local/tmp/setup.sh
    adb shell sh /data/local/tmp/setup.sh
    Plain content call commands (no JSON) can be typed directly without a script. In Git Bash, prefix the adb lines with MSYS_NO_PATHCONV=1 so the on-device path isn't rewritten.
  • Changes didn't appear? The target app must restart — activating a profile does this for you, and clears that app's data in the same step, so it comes up with the new identity and nothing carried over from before.
  • PROXY_INVALID for a proxy you trust? The device needs internet for the test, capped at ~8s, so a slow proxy can time out.
  • BUSY from a job you already cancelled? backup.cancel flips the job to CANCELLED (and sets finishedAt) at once, but the tar it launched cannot be interrupted and keeps running, so the slot stays held. backup.job shows CANCELLED while the slot is still busy, and no field reports the slot — do not treat CANCELLED as “slot free”; just retry profile.setActive / deactivate / delete until they stop returning BUSY. (A job that ends naturally in DONE or FAILED frees the slot almost at once.)
Download