Control API (for developers)
To use the Control API, turn on the Control API toggle in the Group Sync leader section. It shows the host, port, and API key that your code needs to send commands like teleport, walk, or route playback. Your commands will control this device the same way taps and swipes do — followers see the same movement signals. See the reference below for endpoints and authentication.
Overview
The Control API is a local HTTP server running only on the group leader. It listens on the host and port shown in the app.
All requests must include Authorization: Bearer <api-key> (from the Control API toggle).
Request and response bodies are JSON.
The base URL is http://<host>:<port>/api/v1/.
Authentication
Every request requires an Authorization header:
Authorization: Bearer <api-key>
If the key is incorrect or missing, the server responds with 401 Unauthorized and the header WWW-Authenticate: Bearer.
If the Control API is off, all /api/v1/ paths return 404 Not Found.
Example request:
curl -H "Authorization: Bearer abcdef123456..." \
http://192.168.1.100:8080/api/v1/status
Errors
All error responses use the same structure: a JSON object with an error object containing code (machine-readable identifier) and message (human-readable description).
Example:
{ "error": { "code": "bad_request", "message": "lat must be -90..90 and lon -180..180" } }
Status codes and their meanings:
400 Bad Request— Invalid input or malformed JSON body.401 Unauthorized— Missing or incorrect API key.404 Not Found— Endpoint or resource does not exist, or Control API is off.405 Method Not Allowed— Wrong HTTP method (e.g.,GETinstead ofPOST).409 Conflict— State conflict (e.g., a route is playing when you try to start roaming, or the route you are trying to edit is currently playing).413 Payload Too Large— Request body exceeds 64 KiB.500 Internal Error— Server error; try again later.
State and Status
/statusReturns the API version, the device's role (always "leader"), and the number of active followers.
Returns {"apiVersion":1,"role":"leader","followers":N}.
/stateFull state snapshot: spoofing state, movement mode, current position, bearing, speed, active route, walk target, roaming status, and active speed profile.
Returns a JSON object with keys spoofState, mode, position, bearing, speedMs, activeRouteId, walk, roaming, speedProfileId. Null fields use JSON null.
/positionCurrent position only.
Returns {"lat":double,"lon":double}. Returns 409 Conflict if spoofing is not running.
Movement Commands
/teleportTeleport to the given coordinates. Followers will follow or walk there depending on their Follow leader teleports setting. Cooldown is advisory only — the request succeeds and returns a warning field (see example below) if you are within the suggested teleport cooldown, but the teleport runs anyway.
lat- double required
lon- double required
Returns 409 Conflict if spoofing is not running.
/walkWalk to the given coordinates. Followers walk toward the same target at their own speed.
lat- double required
lon- double required
viaRoads- boolean optional Set to
trueto follow roads (Guided routing) or omit it / leave itfalsefor a straight line.
/walk/pausePause the current walk without stopping it. The position is frozen.
/walk/resumeResume a paused walk.
/walk/stopStop the walk and clear the target.
/route/startStart playing a route. All options are optional and default to the route's saved settings. Followers walk or snap to waypoints depending on their teleport setting.
routeId- string required
loop- boolean optional
reverse- boolean optional
returnToLocation- boolean optional
followRoadsToStart- boolean optional
planting- boolean optional
teleportBetweenWaypoints- boolean optional
teleportBetweenDelaySeconds- integer optional
Returns 404 Not Found if the route does not exist.
/route/pausePause the current route replay.
/route/resumeResume a paused route replay.
/route/stopStop route replay and clear the active route.
/roam/startStart roaming with optional overrides. All other fields default to the app's saved roaming defaults.
lat- double optional If
latandlonare omitted, roaming starts from the current position. lon- double optional
radiusMeters- double optional
distanceMeters- double optional
speedProfileId- string optional
followRoads- boolean optional
returnToInitialLocation- boolean optional
kind- string optional Can be
WAYPOINT(random points) orPLANTING(grid pattern). plantingStartRadiusMeters- double optional
plantingEndRadiusMeters- double optional
plantingInfiniteLoops- boolean optional
plantingLoopCount- integer optional
plantingSpeedProfileId- string optional
Returns 409 Conflict if a route is currently playing.
/roam/pausePause roaming.
/roam/resumeResume roaming.
/roam/stopStop roaming.
/spoofing/startStart spoofing a fake location. Does nothing if spoofing is already running.
/spoofing/stopStop spoofing. The foreground service continues running if any automated movement (walk, route, or roam) is active.
/speed-profileSet the active speed profile (e.g. "slow_walk", "walk", "run", "bike", "drive").
id- string required
Returns 400 Bad Request if the profile ID is unknown.
/joystickSimulate a joystick hold: move in the given direction at the given force (0–1) for the given duration (milliseconds). To release immediately, send force 0 or durationMs 0. A new request replaces any running hold.
bearingDegrees- double required
force- double required 0–1.
durationMs- long required Milliseconds. The maximum duration is 10 seconds.
Content API (Favorites, Routes, Speed Profiles)
/favoritesList all favorites.
Response: array of objects with id, name, lat, lon, createdAt (milliseconds since epoch), and category (string or null).
/favoritesCreate a new favorite.
name- string required Required and cannot be empty.
lat- double required
lon- double required
category- string optional
Returns 201 Created with the new favorite object (includes id and createdAt).
/favorites/{id}Get a single favorite by ID.
Returns 404 Not Found if not found.
/favorites/{id}Update a favorite. Preserves id and createdAt.
name- string required Required and cannot be empty.
lat- double required
lon- double required
category- string optional
Returns 200 OK. Returns 404 Not Found if not found.
/favorites/{id}Delete a favorite.
Returns 404 Not Found if not found.
/routesList all routes (excludes the internal paste-temp scratch route).
Response: array of route objects with id, name, routeType, isLooping, speedProfileId (string or null), randomizeTeleportOrder, createdAt, updatedAt, and waypoints (array of {id,lat,lon,orderIndex,waitSeconds}).
/routesCreate a new route.
name- string required Required.
routeType- string optional One of
STRAIGHT,GUIDED,TELEPORT. isLooping- boolean optional
speedProfileId- string optional
randomizeTeleportOrder- boolean optional
waypoints- array of objects required Each item is
{lat,lon,waitSeconds?}. At least 2 waypoints required.
Returns 201 Created with the new route object.
/routes/{id}Get a single route by ID.
Returns 404 Not Found if not found or if it is the internal scratch route.
/routes/{id}Update a route (entire waypoint list). Body: same as POST /routes. Preserves id and createdAt, updates updatedAt.
name- string required Required.
routeType- string optional One of
STRAIGHT,GUIDED,TELEPORT. isLooping- boolean optional
speedProfileId- string optional
randomizeTeleportOrder- boolean optional
waypoints- array of objects required Each item is
{lat,lon,waitSeconds?}. At least 2 waypoints required.
Returns 200 OK. Returns 404 Not Found if not found, or 409 Conflict if the route is currently playing.
/routes/{id}Delete a route.
Returns 404 Not Found if not found, or 409 Conflict if the route is currently playing.
/speed-profilesList the five built-in speed profiles.
Response: array of objects with id, name, speedMetersPerSecond, builtIn (always true), active (boolean flag), and enabled (boolean flag).
/speed-profiles/{id}Get a single profile by ID. Profile IDs: slow_walk, walk, run, bike, drive.
Returns 404 Not Found if not found.
/speed-profiles/{id}Update a speed profile's speed. Profile ID, name, and builtIn flag cannot be changed.
speedMetersPerSecond- double required Must be between 0.01 and 15.0.
Returns 200 OK with the updated profile. Returns 400 Bad Request if the speed is out of range, or 404 Not Found if the profile does not exist.
Security Limits
- Local network only: The API is accessible only over the same Wi-Fi network. It does not work over the internet or mobile data.
- Plain HTTP, no encryption: Requests travel in clear text. Do not send the API key over an untrusted network.
- API key in clear: The key must be sent in the
Authorizationheader with each request. Treat it like a password. - Off by default: The Control API is disabled when you create or join a group. Turn it on explicitly in the Control API section to allow other apps to connect.
- Resets on exit: Leaving the group or restarting the app turns the Control API off. The key persists; you can turn the API back on next time.
- Key rotation: Tap Regenerate key in the Control API section to create a new key. The old key stops working immediately.
- Hide Teleport is bypassed: The Control API ignores the Hide Teleport setting. Routes and manual teleports through the API work regardless. You are responsible for the security implications of enabling the API.
- Cooldown is advisory: Teleport requests that violate the suggested cooldown still execute; they return a
warningfield but do not fail.
Example: Teleport via curl
Here is a complete example using curl to teleport the leader to Times Square in New York:
curl -X POST http://192.168.1.100:8080/api/v1/teleport \
-H "Authorization: Bearer abcdef123456..." \
-H "Content-Type: application/json" \
-d '{"lat":40.758,"lon":-73.9855}'
If successful, the response is:
{"ok":true}
If the device has moved recently and is in a cooldown period, the response includes a warning:
{"ok":true,"warning":{"code":"teleport_cooldown","message":"Suggested teleport cooldown has not elapsed","remainingSeconds":45,"totalSeconds":120,"distanceMeters":5280}}