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+, the whole backup / restore group needs 1.5.3+, the SIM & device generator (simCountries,deviceTemplate,sim.countries,device.templates) needs 1.5.9+,hook_sensor_gyroscopeneeds 1.6.3+, andhook_sensor_lightneeds 1.6.4+. 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, orsim.countries/device.templatesbefore 1.5.9, 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, simCountries, built-in deviceTemplate ids) | adb shell content call |
am broadcast | JSON objects (hookFlags, proxy, deviceMock, sim) and custom: template ids — 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 seventeen — status, every read (sim.countries and device.templates included), 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 nine extras — name, hookFlags, proxy, geo, deviceMock, sim, gmails, and since 1.5.9 simCountries and deviceTemplate — so name, gmails and simCountries 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 nine 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.9"}, 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). |
simCountries | string[] | optional | Generate the SIM cards from 1–2 country codes instead of writing sim by hand (either transport). Needs app 1.5.9+ — see SIM & device generator. |
deviceTemplate | string | optional | Generate deviceMock from a template — random or an id from device.templates (content call; broadcast for custom: ids). Needs app 1.5.9+. |
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.
SIM & device from the built-in database
New in 1.5.9. Configuration below shows how to hand-write a sim or deviceMock block field by field. This group lets you skip that: two extra profile.update keys — simCountries and deviceTemplate — make the app generate the whole block from the same carrier and device database its own UI draws from, and two read methods list what it can generate from. The result is saved into the profile with enabled:true already set, so there is nothing else to send — read it back with profile.get.
⚠️ One way per setting, per call.
simandsimCountriesin the same request isBAD_REQUEST, and so isdeviceMocktogether withdeviceTemplate. Mixing across settings is fine —simCountriesnext to a hand-writtendeviceMock, or both generators at once, as in the first example below. The call is atomic: everything is validated before anything is saved, so a bad template id fails the whole request and thesimCountriessent with it is not applied either.
READ sim.countries List the countries the SIM generator knows content call
Parameters
No parameters.
Request
adb shell content call --uri content://com.wowsoftware.hidemyandroid.headless --method sim.countries --extra token:s:<your_access_token> Response ok=true
Bundle[{data=[{"code":"AF","country":"Afghanistan"},{"code":"AL","country":"Albania"},...,{"code":"GB","country":"United Kingdom"},...,{"code":"US","country":"United States"},...], ok=true}] Only countries with at least one carrier in the database are listed, sorted by name. code is the uppercase ISO 3166-1 alpha-2 value that simCountries expects; country is the English name as stored ("United Kingdom", "United States").
READ device.templates List the device templates — built-in and imported content call
Parameters
No parameters.
Request
adb shell content call --uri content://com.wowsoftware.hidemyandroid.headless --method device.templates --extra token:s:<your_access_token> Response ok=true
Bundle[{data=[{"id":"asus|ASUS_AI2401|AI2401_A","manufacturer":"asus","label":"ROG Phone 8","isCustom":false},...,{"id":"custom:samsung|e3qxeea|SM-S928B","manufacturer":"samsung","label":"Galaxy S24 Ultra","isCustom":true}], ok=true}] One entry per template. id is what deviceTemplate takes — brand|product|model, with custom: in front for templates you imported in the app (isCustom true); label is the marketing name (ROG Phone 8); manufacturer is kept exactly as the OEM spells it (asus, samsung, OnePlus), not normalised. See the transport note below for custom: ids.
UPDATE profile.update Generate SIM cards and/or a device from the database content call / broadcast
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
id | string | required | The profile id. |
simCountries | string[] | optional | JSON array of 1–2 ISO alpha-2 codes, e.g. ["US","GB"] — one per SIM slot, in order. content call or broadcast. |
deviceTemplate | string | optional | random (or an empty string) for a random template, built-in or imported; or one id from device.templates. Built-in ids: content call or broadcast. custom: ids: broadcast only. |
Request
broadcast — both generators in one call (as tested)
adb shell am broadcast -a com.wowsoftware.hidemyandroid.HEADLESS_UPDATE -p com.wowsoftware.hidemyandroid -e token <your_access_token> -e id <profile_id> -e simCountries '["US","GB"]' -e deviceTemplate random content call — simCountries only, slot 0 only (single quotes are required)
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 'simCountries:s:["US"]' content call — a random template
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 deviceTemplate:s:random content call — one specific built-in template (single quotes are required: | is a shell pipe)
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 'deviceTemplate:s:asus|ASUS_AI2401|AI2401_A' broadcast — an imported (custom:) template
adb shell am broadcast -a com.wowsoftware.hidemyandroid.HEADLESS_UPDATE -p com.wowsoftware.hidemyandroid -e token <your_access_token> -e id <profile_id> -e deviceTemplate 'custom:samsung|e3qxeea|SM-S928B' Response ok=true · full updated profile
Returns the full updated profile, like any profile.update — the generated sim.simCards and deviceMock are right there in the response.
⚠️ Every call rolls fresh values: repeating the same simCountries gives new numbers, and deviceTemplate random can land on a different device each time. Send only the key you want regenerated.
simCountries
- Format. A JSON array of ISO 3166-1 alpha-2 codes —
["US","GB"], case-insensitive. Position is the slot: the first code fills slot0, the second slot1. Anything that is not a JSON array, an empty array, a blank entry, more than two entries, or a code thatsim.countriesdoes not list returnsBAD_REQUEST— a third code is refused, never silently dropped the way a thirdsimcard is. - What you get. For each slot the app picks a carrier of that country from its database and fills every field of the hand-written
simblock:carrierName,countryIso,mcc,mnc,operatorCode, a valid E.164 mobilephoneNumberwith the country dialling code (+1…for US), aniccidandimsiprefixed with that carrier's MCC+MNC, and anetworkTypedrawn at random fromLTE,NR 5G,WCDMAandGSM. Then it setssim.enabledtotrueand saves. - It replaces the whole SIM block. As with
sim, the result is normalised to two cards:["US"]fills slot0and leaves slot1empty, whatever was in it before.
deviceTemplate
random(or an empty string) — the app rolls one template from everything it knows, built-in and imported alike. Note the difference between empty and absent:-e deviceTemplate ""means random, while leaving the key out altogether leaves the device untouched.- A template id from
device.templates— that device, every time. Built-in ids arebrand|product|model, e.g.asus|ASUS_AI2401|AI2401_A; templates you imported in the app carry the same shape withcustom:in front, e.g.custom:samsung|e3qxeea|SM-S928B. An id that is not in the list returnsBAD_REQUEST. - What you get. Every
deviceMockfield filled from the template,deviceMock.enabledset totrue, and the profile saved.
Which transport
The usual colon rule applies. sim.countries, device.templates, simCountries and built-in template ids contain no :, so content call carries them. Single-quote the value whenever it contains |, [, ] or " — that is every simCountries array and every template id. Without the quotes the device shell reads | as a pipe and fails before content even runs (sh: e3qxeea: not found, exit 127). A custom id starts with custom:, and that extra colon breaks the key:type:value form of --extra: the content tool itself rejects it with [ERROR] Binding not well formed and the request never reaches the app — there is no BAD_REQUEST to catch. Send custom ids through the HEADLESS_UPDATE broadcast; nothing else about the call changes.
Configuration
These are the values you send with profile.update. Defaults when a profile is created:
| Setting | Default | To use it… |
|---|---|---|
| hookFlags (all 23) | ✅ 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 — or send deviceTemplate instead and let the app fill it (1.5.9+) |
| sim | ❌ OFF | Include "enabled":true — or send simCountries instead and let the app fill it (1.5.9+) |
| 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 22 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_sensor_gyroscope | Spoof the gyroscope (needs hook_realistic_sensor). Needs app 1.6.3+. |
hook_sensor_light | Spoof the ambient light sensor (needs hook_realistic_sensor). Needs app 1.6.4+. The light environment preset (Indoor, On the move, Outdoors, Night / dark) can only be picked in the app; a profile created through the Headless API starts on Indoor, and a headless update leaves an existing choice untouched. |
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. On 1.5.9+ you can skip this block entirely and send deviceTemplate instead — random rolls a device from the app's own database, an id from device.templates picks a specific one — and the app fills every field and enables it for you.
| 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"}' Or, on 1.5.9+, let the app roll a random device from its database — no JSON at all (random draws from built-in and imported templates alike; put an id from device.templates in its place to pick one device):
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 deviceTemplate:s:random 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. On 1.5.9+ you can skip this block entirely and send simCountries instead — the app generates real carrier data for each slot and enables it for you. 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"}]}' Or, on 1.5.9+, let the app generate both cards from country codes — no JSON at all (slot 0 gets the first code, slot 1 the second; one code fills slot 0 only):
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 'simCountries:s:["US","GB"]' 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:["[email protected]","[email protected]"]' Full example
A profile from zero to active, one step at a time. Steps 5, 6 and 7 each offer several ways to the same result — pick one per step; the tabs switch between them. Every command is shown in its adb shell form. At the end, the two recipes condense the whole sequence into a single copy-and-run block, and Troubleshooting has the same thing as an on-device script.
At a glance
- 01 Check the API is ready
- 02 Create the profile
- 03 Add the target apps
- 04 Add virtual Gmail accounts
- 05 Choose the device identity
- 06 Choose the SIM cards
- 07 Network and location
- 08 Tune the hook flags
- 09 Back up the outgoing profile
- 10 Activate
- 11 Verify
- 12 Later: clone, stop, remove
Placeholders used below
<your_access_token>— the key from Settings → Developer → Headless API<profile_id>— the id that step 2 returns<active_profile_id>/<job_id>— step 9 only, fromstatusandbackup.start
One read before anything else. It tells you whether Premium, the feature switch and the licence freshness are all in order — the three things every other call is gated on.
adb shell content call --uri content://com.wowsoftware.hidemyandroid.headless --method status --extra token:s:<your_access_token>
# expect "premium":true, "featureEnabled":true, "freshnessOk":true
# anything else: fix that first — see Status and Error codes above Everything else hangs off the id this returns. A new profile starts with every hook flag ON (except hook_spoof_uptime), no target apps, and proxy, deviceMock and sim all OFF.
adb shell content call --uri content://com.wowsoftware.hidemyandroid.headless --method profile.create --extra token:s:<your_access_token> --extra name:s:Demo
# → Bundle[{data={"id":"a1b2c3d4-..."}, ok=true}]
# copy that id — it is <profile_id> in every command below Only the apps on this list are spoofed. Add each one by package name; anything not listed keeps seeing the real device.
# one call per app — the package name, not the display name
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
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.instagram.android
# check the list (adding the same package twice returns BAD_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> The addresses the target apps will see as signed-in Google accounts. Needs hook_virtual_accounts, which is ON by default. Skip it and the apps see no Google account at all.
# @gmail.com only; the list replaces the previous one, [] clears it
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:["[email protected]","[email protected]"]' Three ways to the same result — a deviceMock block with enabled:true. Let the app roll a device, name a template, or write every field yourself.
# 1.5.9+ — one call, no JSON: the app rolls a device from its database
# (built-in and imported templates alike) and switches deviceMock on for you
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 deviceTemplate:s:random # 1.5.9+ — first see what is available; "id" is what you send back
adb shell content call --uri content://com.wowsoftware.hidemyandroid.headless --method device.templates --extra token:s:<your_access_token>
# → [{"id":"asus|ASUS_AI2401|AI2401_A","manufacturer":"asus","label":"ROG Phone 8","isCustom":false}, ...]
# built-in id → content call works — single quotes are REQUIRED, | is a shell pipe
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 'deviceTemplate:s:asus|ASUS_AI2401|AI2401_A'
# imported (custom:) id → broadcast ONLY; the extra colon breaks content call
adb shell am broadcast -a com.wowsoftware.hidemyandroid.HEADLESS_UPDATE -p com.wowsoftware.hidemyandroid -e token <your_access_token> -e id <profile_id> -e deviceTemplate 'custom:samsung|e3qxeea|SM-S928B' # any version — write every Build field yourself; "enabled":true is the switch
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"}' A and B need app 1.5.9+ and never go in the same call as C. Running one after another is fine — the last call wins, and A / B roll fresh values every time they run.
Same idea for sim: generate the cards from country codes, or write both cards yourself. Either way the profile ends up with exactly two slots.
# 1.5.9+ — see which countries the generator knows ("code" is what you send)
adb shell content call --uri content://com.wowsoftware.hidemyandroid.headless --method sim.countries --extra token:s:<your_access_token>
# → [{"code":"AF","country":"Afghanistan"}, ..., {"code":"GB","country":"United Kingdom"}, ..., {"code":"US","country":"United States"}, ...]
# two cards: slot 0 = US, slot 1 = GB — carrier, number, ICCID, IMSI all generated
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 'simCountries:s:["US","GB"]'
# or a single card — slot 1 is left empty
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 'simCountries:s:["US"]' # any version — two cards, every field yourself; a third card is dropped silently
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"}]}' # 1.5.9+ — device (step 5) and SIM (step 6) in ONE broadcast: validated together, saved together
adb shell am broadcast -a com.wowsoftware.hidemyandroid.HEADLESS_UPDATE -p com.wowsoftware.hidemyandroid -e token <your_access_token> -e id <profile_id> -e simCountries '["US","GB"]' -e deviceTemplate random A and C need app 1.5.9+. sim and simCountries in one call is BAD_REQUEST. C is atomic — a bad template id fails the whole call and the SIM is not applied either.
Where the traffic goes and where the device claims to be are two separate settings. proxy routes the traffic and, by default, also supplies the location; geo with source: CUSTOM sets the location by hand and needs no proxy at all. Skip this step and the profile keeps the real IP and the real location.
# the proxy is tested live (device needs internet, up to ~8s);
# one that fails returns PROXY_INVALID and is not saved
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"}'
# with auth: add "username":"…","password":"…" — never echoed back by profile.get
# location follows the proxy by default; this line only matters if geo was CUSTOM before
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"}' # no proxy at all — the location is yours; coordinates, countryCode and timezone are all required
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"}'
# timezone is NOT validated: a typo silently becomes GMT — copy the IANA name exactly # traffic through the proxy, GPS / timezone / locale from your own values —
# a deliberate mismatch (IP in one country, GPS in another); geo.source decides what the hooks use
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"}'
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"}' A proxy profile needs the system VPN consent once, granted from the app's Headless API screen — without it activation still succeeds, but the proxy will not connect and the call returns INTERNAL.
Every flag except hook_spoof_uptime is already ON, so most profiles skip this. Do it only to switch specific hooks off, or uptime spoofing on. Because an update replaces the whole map, read it first and send it back complete.
# every flag is already ON (except hook_spoof_uptime), so most profiles skip this step.
# an update REPLACES the whole map — read it first, then send it back complete:
adb shell content call --uri content://com.wowsoftware.hidemyandroid.headless --method profile.get --extra token:s:<your_access_token> --extra id:s:<profile_id>
# example: Wi-Fi + Bluetooth spoofing off, uptime spoofing on, everything else at its 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 hookFlags '{"hook_hide_dev_opts":true,"hook_hide_vpn":true,"hook_hide_airplane_mode":true,"hook_hide_proxy":true,"hook_hide_root":true,"hook_hide_lsposed":true,"hook_spoof_installer":true,"hook_package_info":true,"hook_lan_scan_block":true,"hook_identifiers_partly":true,"hook_identifiers_fully":true,"hook_wifi":false,"hook_nearby_wifi":true,"hook_nearby_bluetooth":false,"hook_realistic_sensor":true,"hook_sensor_accelerometer":true,"hook_sensor_gyroscope":true,"hook_sensor_light":true,"hook_spoof_uptime":true,"hook_virtual_accounts":true,"hook_geo_gps":true,"hook_geo_locale":true,"hook_geo_timezone":true}' The only safety net there is: activating the new profile wipes the target apps' data. If the profile that is active right now holds sessions or data worth keeping, back it up before you switch. Both *.start calls work on the active profile only.
# activating wipes the target apps' data — if the CURRENTLY active profile has anything worth keeping, save it first
adb shell content call --uri content://com.wowsoftware.hidemyandroid.headless --method status --extra token:s:<your_access_token>
# → "activeProfileId" is the profile to back up
adb shell content call --uri content://com.wowsoftware.hidemyandroid.headless --method backup.start --extra token:s:<your_access_token> --extra id:s:<active_profile_id>
# → {"jobId":"f0e1d2c3-...", "type":"backup", ...} — poll until "state" is DONE (or FAILED):
adb shell content call --uri content://com.wowsoftware.hidemyandroid.headless --method backup.job --extra token:s:<your_access_token> --extra jobId:s:<job_id>
# later, once that profile is active again, pour it back:
# adb shell content call --uri content://com.wowsoftware.hidemyandroid.headless --method restore.start --extra token:s:<your_access_token> --extra id:s:<active_profile_id> The moment the identity goes live. Whatever profile was active before is deactivated for you — there is no need to call profile.deactivate first.
# force-stops every target app, CLEARS ITS DATA, and brings it back with the new identity.
# no confirmation dialog — this comment is the only warning you get
adb shell content call --uri content://com.wowsoftware.hidemyandroid.headless --method profile.setActive --extra token:s:<your_access_token> --extra id:s:<profile_id>
# → Bundle[{data={"id":"a1b2...","isActive":true}, ok=true}]
# BUSY? a backup / restore job still holds the slot — retry until it stops Read the profile back and confirm what the hooks will use: the generated or hand-written blocks with enabled:true, the proxy with its live-test output, and geo pointing at the source you chose.
adb shell content call --uri content://com.wowsoftware.hidemyandroid.headless --method profile.get --extra token:s:<your_access_token> --extra id:s:<profile_id>
# → "isActive":true, "deviceMock":{"enabled":true,...}, "sim":{"enabled":true,"simCards":[...]}, "proxy":{...}, "geo":{...}
adb shell content call --uri content://com.wowsoftware.hidemyandroid.headless --method status --extra token:s:<your_access_token>
# → "activeProfileId":"<profile_id>" The profile is done. These are the calls you will reach for next.
# a second identity with the same settings — device re-rolled, SIM numbers regenerated, name generated
adb shell content call --uri content://com.wowsoftware.hidemyandroid.headless --method profile.duplicate --extra token:s:<your_access_token> --extra id:s:<profile_id>
# stop spoofing (no id needed — it deactivates whichever profile is active)
adb shell content call --uri content://com.wowsoftware.hidemyandroid.headless --method profile.deactivate --extra token:s:<your_access_token>
# delete the profile — its backups under /sdcard/HideMyAndroid_Backups/ go with it
adb shell content call --uri content://com.wowsoftware.hidemyandroid.headless --method profile.delete --extra token:s:<your_access_token> --extra id:s:<profile_id> Two complete recipes
The steps above, each condensed into one copy-and-run block. A is the shortest path on 1.5.9+; B avoids the generator and the proxy entirely and works on 1.4.5+. Replace <profile_id> after the first command in either.
Recipe A — everything generated
Random device, SIM cards for US + GB, traffic and location through a proxy. Seven commands.
# Recipe A — everything generated (app 1.5.9+). The shortest path to a working profile.
# 1. create — 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. 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. virtual Gmail account
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:["[email protected]"]'
# 4. device + SIM in one call — a random device, slot 0 US, slot 1 GB (both enabled and saved)
adb shell am broadcast -a com.wowsoftware.hidemyandroid.HEADLESS_UPDATE -p com.wowsoftware.hidemyandroid -e token <your_access_token> -e id <profile_id> -e simCountries '["US","GB"]' -e deviceTemplate random
# 5. proxy — tested live (~8s); the location follows 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 proxy '{"enabled":true,"host":"10.0.0.1","port":1080,"protocol":"SOCKS5"}'
# 6. activate — clears the target app's 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>
# 7. 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> Recipe B — everything by hand
Every Build field, every SIM field and the location written by you; no proxy, no generator.
# Recipe B — everything by hand (app 1.4.5+). No generator, no proxy: you control every value.
# 1. create — 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. 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. 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:["[email protected]","[email protected]"]'
# 4. device — every Build field yourself
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. SIM — two cards, every field yourself
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. location — custom, no proxy (all four fields required; timezone is not validated)
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"}'
# 7. activate — clears the target app's 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>
# 8. 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, a country code / template id that the discovery calls list — and never sim with simCountries or deviceMock with deviceTemplate in one call |
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 — Recipe A from 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:["[email protected]","[email protected]"]' # 4. device + SIM generated from the database (1.5.9+): random device, slot 0 US, slot 1 GB am broadcast -a $PKG.HEADLESS_UPDATE -p $PKG -e token $K -e id $ID -e simCountries '["US","GB"]' -e deviceTemplate random # Recipe B alternative (any version) — write both blocks yourself instead: # 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"}' # 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"}]}' # 5. enable + set the proxy (tested live, ~8s) — the location follows it 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"}' # Or skip the proxy and set the location yourself (use INSTEAD of the proxy line, 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"}' # 6. activate (restarts the target app and clears its data) content call --uri $URI --method profile.setActive --extra token:s:$K --extra id:s:$ID # 7. 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.)Binding not well formedfor a template id you copied straight fromdevice.templates? Imported templates have ids that start withcustom:, and that colon breaks thekey:type:valueform of--extra— thecontenttool prints[ERROR] Binding not well formedplus its usage text and the request never reaches the app. Send it with theHEADLESS_UPDATEbroadcast instead (-e deviceTemplate 'custom:…'). Built-in ids (brand|product|model) have no colon and work over either transport — but single-quote them, or the shell reads|as a pipe (sh: e3qxeea: not found) and nothing is sent at all.