HideMyAndroid Headless API

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

Last updated: September 2026

Requirements

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

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

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

Authentication

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

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

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

Making requests

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

Transport Use for Command
content call Reads, and values without : (name, gmails, package names, 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

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

Response ok=true

Bundle[{data={"premium":true,"featureEnabled":true,"freshnessOk":true,"activeProfileId":"a1b2c3d4-...","version":"1.5.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.

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

Profiles

READ profile.list List all profiles content call

Parameters

No parameters.

Request

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

Response ok=true

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

Parameters

Name Type Required Description
id string required The profile id.

Request

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

Response ok=true

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

Parameters

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

Request

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

Response ok=true

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

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

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

Parameters

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

Request

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

Response ok=true

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

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

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

What a duplicate carries over

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

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

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

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

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

Parameters

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

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

broadcast — JSON configs

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

Response ok=true

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

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

DELETE profile.delete Delete a profile permanently content call

Parameters

Name Type Required Description
id string required The profile id.

Request

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

Response ok=true

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

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

Activation

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

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

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

Parameters

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

Request

content call — recommended

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

broadcast — for automation

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

Response ok=true

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

Parameters

No parameters.

Request

content call — recommended

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

broadcast — for automation

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

Response ok=true

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

No id needed — it deactivates whichever profile is active.

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

Target apps

Target apps are the apps a profile spoofs.

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

Parameters

Name Type Required Description
id string required The profile id.

Request

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

Response ok=true

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

Parameters

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

Request

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

Response ok=true · updated app list

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

⚠️ Adding the same package twice returns BAD_REQUEST.

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

Parameters

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

Request

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

Response ok=true · updated app list

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

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

Backup & restore

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

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

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

Parameters

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

Request

content call — every target app

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

content call — selected packages only

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

Response ok=true · job accepted

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

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

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

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

Parameters

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

Request

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

Response ok=true · job accepted

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

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

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

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

Parameters

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

Request

content call — a specific job

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

content call — the latest job (no jobId)

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

Response ok=true

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

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

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

ACTION backup.cancel Cancel a running job content call

Parameters

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

Request

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

Response ok=true · job snapshot

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

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

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

Parameters

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

Request

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

Response ok=true

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

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

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

Parameters

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

Request

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

Response ok=true

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

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

How jobs behave

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

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. sim and simCountries in the same request is BAD_REQUEST, and so is deviceMock together with deviceTemplate. Mixing across settings is fine — simCountries next to a hand-written deviceMock, 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 the simCountries sent with it is not applied either.

READ sim.countries List the countries the SIM generator knows content call

Parameters

No parameters.

Request

bash
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

bash
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)

bash
adb shell am broadcast -a com.wowsoftware.hidemyandroid.HEADLESS_UPDATE -p com.wowsoftware.hidemyandroid -e token <your_access_token> -e id <profile_id> -e simCountries '["US","GB"]' -e deviceTemplate random

content call — simCountries only, slot 0 only (single quotes are required)

bash
adb shell content call --uri content://com.wowsoftware.hidemyandroid.headless --method profile.update --extra token:s:<your_access_token> --extra id:s:<profile_id> --extra 'simCountries:s:["US"]'

content call — a random template

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

content call — one specific built-in template (single quotes are required: | is a shell pipe)

bash
adb shell content call --uri content://com.wowsoftware.hidemyandroid.headless --method profile.update --extra token:s:<your_access_token> --extra id:s:<profile_id> --extra 'deviceTemplate:s:asus|ASUS_AI2401|AI2401_A'

broadcast — an imported (custom:) template

bash
adb shell am broadcast -a com.wowsoftware.hidemyandroid.HEADLESS_UPDATE -p com.wowsoftware.hidemyandroid -e token <your_access_token> -e id <profile_id> -e 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 slot 0, the second slot 1. Anything that is not a JSON array, an empty array, a blank entry, more than two entries, or a code that sim.countries does not list returns BAD_REQUEST — a third code is refused, never silently dropped the way a third sim card 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 sim block: carrierName, countryIso, mcc, mnc, operatorCode, a valid E.164 mobile phoneNumber with the country dialling code (+1… for US), an iccid and imsi prefixed with that carrier's MCC+MNC, and a networkType drawn at random from LTE, NR 5G, WCDMA and GSM. Then it sets sim.enabled to true and saves.
  • It replaces the whole SIM block. As with sim, the result is normalised to two cards: ["US"] fills slot 0 and leaves slot 1 empty, 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 are brand|product|model, e.g. asus|ASUS_AI2401|AI2401_A; templates you imported in the app carry the same shape with custom: in front, e.g. custom:samsung|e3qxeea|SM-S928B. An id that is not in the list returns BAD_REQUEST.
  • What you get. Every deviceMock field filled from the template, deviceMock.enabled set to true, 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:false is stored but inert — values are saved so you can flip it on later, but nothing is spoofed until enabled:true. geo is the exception: it has no enabled switch. The hook_geo_* flags apply whenever geo resolves to a usable location — either from source: CUSTOM, or from a proxy that is enabled:true and passed its live test.

hookFlags

Default: ON for every flag except hook_spoof_uptime, which is OFF. A map of "flag": true|false. An update replaces the whole map, but only the map you actually send: an update that omits the hookFlags extra altogether leaves every flag exactly as it was. Within a map you do send, every flag you leave out returns to its own default, so the other 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 — 165535, checked only when you enable the proxy
protocol string HTTP HTTP, SOCKS4, or SOCKS5
username string "" Auth (optional) — never returned by profile.get
password string "" Auth (optional) — never returned by profile.get
countryCode string "" Output — 2-letter country, filled from the live test
lat double 0 Output — filled from the live test
lon double 0 Output — filled from the live test
timezone string "" Output — IANA name, filled from the live test
bash
adb shell am broadcast -a com.wowsoftware.hidemyandroid.HEADLESS_UPDATE -p com.wowsoftware.hidemyandroid -e token <your_access_token> -e id <profile_id> -e proxy '{"enabled":true,"host":"10.0.0.1","port":1080,"protocol":"SOCKS5"}'

geo

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

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

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

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

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

CUSTOM — set a location by hand, no proxy needed

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

PROXY — take the location from the proxy (default)

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

deviceMock

Default: OFF. Pretends the device is a different model. Set "enabled":true plus any of these fields — all optional strings (default empty), each mapping to an Android Build property. 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
bash
adb shell am broadcast -a com.wowsoftware.hidemyandroid.HEADLESS_UPDATE -p com.wowsoftware.hidemyandroid -e token <your_access_token> -e id <profile_id> -e deviceMock '{"enabled":true,"manufacturer":"Samsung","brand":"samsung","model":"SM-S918B","device":"dm3q","product":"dm3qxxx","board":"kalama","hardware":"qcom","buildId":"UP1A.231005.007","buildIncremental":"S918BXXU3CWK9","buildType":"user","buildTags":"release-keys","buildFingerprint":"samsung/dm3qxxx/dm3q:14/UP1A.231005.007/S918BXXU3CWK9:user/release-keys","deviceName":"Galaxy S23 Ultra"}'

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):

bash
adb shell content call --uri content://com.wowsoftware.hidemyandroid.headless --method profile.update --extra token:s:<your_access_token> --extra id:s:<profile_id> --extra 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 that NR 5G carries a space: NR5G is not recognised and falls into exactly that silent hole.

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

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):

bash
adb shell content call --uri content://com.wowsoftware.hidemyandroid.headless --method profile.update --extra token:s:<your_access_token> --extra id:s:<profile_id> --extra '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:

bash
adb shell content call --uri content://com.wowsoftware.hidemyandroid.headless --method profile.update --extra token:s:<your_access_token> --extra id:s:<profile_id> --extra 'gmails:s:["[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

  1. 01 Check the API is ready
  2. 02 Create the profile
  3. 03 Add the target apps
  4. 04 Add virtual Gmail accounts
  5. 05 Choose the device identity
  6. 06 Choose the SIM cards
  7. 07 Network and location
  8. 08 Tune the hook flags
  9. 09 Back up the outgoing profile
  10. 10 Activate
  11. 11 Verify
  12. 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, from status and backup.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.

bash
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.

bash
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.

bash
# 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.

bash
# @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.

bash
# 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
bash
# 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'
bash
# 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.

bash
# 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"]'
bash
# 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"}]}'
bash
# 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.

bash
# 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"}'
bash
# 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
bash
# 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.

bash
# 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}'

Back up the outgoing profile

optional app 1.5.3+

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.

bash
# 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.

bash
# 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.

bash
adb shell content call --uri content://com.wowsoftware.hidemyandroid.headless --method profile.get --extra token:s:<your_access_token> --extra id:s:<profile_id>
# → "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.

bash
# 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.

A

Recipe A — everything generated

Random device, SIM cards for US + GB, traffic and location through a proxy. Seven commands.

bash
# 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>
B

Recipe B — everything by hand

Every Build field, every SIM field and the location written by you; no proxy, no generator.

bash
# 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, so content call truncated it. Send it via the HEADLESS_UPDATE broadcast instead.
  • JSON broadcast not applying (often on Windows)? Across a desktop shell and the device shell, the quotes in am broadcast JSON (hookFlags, proxy, deviceMock, sim) are easily stripped, so am misreads the value (you may see it land as a dat=… data URI) and nothing changes. The reliable fix is to keep the JSON off the command line — put the whole workflow in a .sh file (single quotes keep every JSON intact), then push and run it on the device. The script runs on the device, so it can even capture the new profile id and reuse it:
    bash
    # setup.sh — 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:$ID
    then push it to the device and run it:
    bash
    adb push setup.sh /data/local/tmp/setup.sh
    adb shell sh /data/local/tmp/setup.sh
    Plain content call commands (no JSON) can be typed directly without a script. In Git Bash, prefix the adb lines with MSYS_NO_PATHCONV=1 so the on-device path isn't rewritten.
  • Changes didn't appear? The target app must restart — activating a profile does this for you, and clears that app's data in the same step, so it comes up with the new identity and nothing carried over from before.
  • PROXY_INVALID for a proxy you trust? The device needs internet for the test, capped at ~8s, so a slow proxy can time out.
  • BUSY from a job you already cancelled? backup.cancel flips the job to CANCELLED (and sets finishedAt) at once, but the tar it launched cannot be interrupted and keeps running, so the slot stays held. backup.job shows CANCELLED while the slot is still busy, and no field reports the slot — do not treat CANCELLED as “slot free”; just retry profile.setActive / deactivate / delete until they stop returning BUSY. (A job that ends naturally in DONE or FAILED frees the slot almost at once.)
  • Binding not well formed for a template id you copied straight from device.templates? Imported templates have ids that start with custom:, and that colon breaks the key:type:value form of --extra — the content tool prints [ERROR] Binding not well formed plus its usage text and the request never reaches the app. Send it with the HEADLESS_UPDATE broadcast 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.
Download