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_uptimeneeds 1.3.4+,geoandprofile.duplicateneed 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 answersok=true— but an unknown method is louder: calling the backup / restore group on a build before 1.5.3 returnsBAD_REQUESTwitherror_message“Unknown method: …”. If a setting seems to do nothing, check the runningversionwith 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 deviceslists 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
INTERNALsaying 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
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.
💡
statusis the only method that still answers when the licence is expired or stale — every other call returnsLICENSE_EXPIREDorLICENSE_STALEand nothing else. That makes it the way to tell those two apart when a request is refused: readpremiumto see whether Premium genuinely ran out, andfreshnessOkto see whether the app merely needs opening once.statusis still gated byFEATURE_DISABLEDandUNAUTHORIZED, 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
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
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
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
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:
# 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
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
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
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.setActivedoes 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 isbackup.start(1.5.3+) before you switch. And since 1.5.3, while a backup / restore job is still holding the slot,profile.setActiveandprofile.deactivateanswerBUSY— even for a job already cancelled; retry until theBUSYstops.
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
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
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
adb shell content call --uri content://com.wowsoftware.hidemyandroid.headless --method profile.deactivate --extra token:s:<your_access_token> broadcast — for automation
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
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
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
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
*.startwhile one runs returnsBUSY, with the runningjobIdinerror_message. The two*.startcalls also require the target profile to be active — otherwiseREQUIRES_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
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
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
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
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)
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
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
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
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
DONEdoes not mean every app succeeded. It only means the job ran to the end. An app can fail while the job still finishesDONE— readfailedandresults[], always.- Activation waits for jobs now. While a job holds the slot,
profile.setActive,profile.deactivateandprofile.deleteanswerBUSY— even for a job you already cancelled, because the roottarunderneath cannot be stopped mid-file, and no snapshot field reports the slot. A job that ends naturally inDONEorFAILEDfrees the slot almost at once; after a cancel, do not treatCANCELLEDas “slot free” — just retry the call untilBUSYstops. foregroundHeldis an observation, not a promise. It reports whether a foreground service is shielding the job from being killed by the OS. It has been measuredtrueon API 36, but eventrueonly 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.gzarchives (data.tar.gz,data_ext.tar.gz,media.tar.gz, …) plusmetadata.json, written with roottar. 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:falseis stored but inert — values are saved so you can flip it on later, but nothing is spoofed untilenabled:true.geois the exception: it has noenabledswitch. Thehook_geo_*flags apply whenevergeoresolves to a usable location — either fromsource: CUSTOM, or from a proxy that isenabled:trueand 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 — 1–65535, 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 |
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: CUSTOMmust arrive complete: a coordinate pair that is not0/0and in range, a 2-lettercountryCode, and atimezone. Miss any one and the call returnsBAD_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.
🛑
timezoneis the one field that is not really checked. It is only tested for being non-empty — never against the IANA database. A typo likeAsia/Tokyoois accepted, returnsok=true, is echoed back unchanged byprofile.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 withBAD_REQUEST.
CUSTOM — set a location by hand, no proxy needed
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)
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 |
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 thatNR 5Gcarries a space:NR5Gis not recognised and falls into exactly that silent hole.
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:
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:
# 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, socontent calltruncated it. Send it via theHEADLESS_UPDATEbroadcast instead. - JSON broadcast not applying (often on Windows)? Across a desktop shell and the device shell, the quotes in
am broadcastJSON (hookFlags,proxy,deviceMock,sim) are easily stripped, soammisreads the value (you may see it land as adat=…data URI) and nothing changes. The reliable fix is to keep the JSON off the command line — put the whole workflow in a.shfile (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:bashthen push it to the device and run it:# 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:$IDbashPlainadb push setup.sh /data/local/tmp/setup.sh adb shell sh /data/local/tmp/setup.shcontent callcommands (no JSON) can be typed directly without a script. In Git Bash, prefix theadblines withMSYS_NO_PATHCONV=1so 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_INVALIDfor a proxy you trust? The device needs internet for the test, capped at ~8s, so a slow proxy can time out.BUSYfrom a job you already cancelled?backup.cancelflips the job toCANCELLED(and setsfinishedAt) at once, but thetarit launched cannot be interrupted and keeps running, so the slot stays held.backup.jobshowsCANCELLEDwhile the slot is still busy, and no field reports the slot — do not treatCANCELLEDas “slot free”; just retryprofile.setActive/deactivate/deleteuntil they stop returningBUSY. (A job that ends naturally inDONEorFAILEDfrees the slot almost at once.)