From letherdo
Automate Android devices and web browsers via the Letherdo API. Create tasks for mobile app automation or web browsing, send instructions, poll for completion, manage schedules, and monitor results. Use when the user wants to automate mobile or browser tasks, interact with Letherdo, or orchestrate device workflows via API.
How this skill is triggered — by the user, by Claude, or both
Slash command
/letherdo:letherdoThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Programmatic access to the Letherdo device automation platform. Create tasks that run on Android emulators or web browsers, send follow-up instructions, poll for results, and manage recurring schedules.
Programmatic access to the Letherdo device automation platform. Create tasks that run on Android emulators or web browsers, send follow-up instructions, poll for results, and manage recurring schedules.
API key required. Check LETHERDO_API_KEY environment variable. If not set, ask the user for their key.
All requests use:
https://app.letherdo.com/apiAuthorization: Bearer <LETHERDO_API_KEY>Use curl, fetch, or any HTTP client. All request/response bodies are JSON.
API keys start with rb_ and are 67 characters long. Generate one at https://app.letherdo.com/settings or via POST /keys.
| Value | Description |
|---|---|
letherdo | Android device automation (default). Controls an Android emulator — tap, type, swipe, install apps. |
browser | Web browser automation. Navigates websites, fills forms, extracts data. |
Set agent_type when creating a task. Default is letherdo.
Every task follows this lifecycle:
curl -X POST https://app.letherdo.com/api/tasks \
-H "Authorization: Bearer $LETHERDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"description": "Open youtube.com and search for Claude AI demos", "agent_type": "browser"}'
Response:
{
"id": "a1b2c3d4",
"status": "assigned",
"agent_type": "browser",
"description": "Open youtube.com and search for Claude AI demos"
}
Status after creation:
browser tasks → assigned immediately (no device needed)letherdo tasks → assigned if an emulator is free, otherwise queuedIf status is queued, poll until it becomes assigned:
curl https://app.letherdo.com/api/tasks/a1b2c3d4 \
-H "Authorization: Bearer $LETHERDO_API_KEY"
Poll every 3-5 seconds. Tasks auto-cancel after 30 minutes in queue.
Once assigned, the agent is working. Poll for events using a cursor:
curl "https://app.letherdo.com/api/tasks/a1b2c3d4/events?after_seq=0" \
-H "Authorization: Bearer $LETHERDO_API_KEY"
Response — array of events ordered by seq:
[
{"seq": 1, "event_type": "task_created", "timestamp": 1712345678.0, "data": {}},
{"seq": 2, "event_type": "llm_call", "timestamp": 1712345680.0, "data": {"model": "claude-opus-4-6", "input_tokens": 500, "output_tokens": 120}},
{"seq": 3, "event_type": "tool_executed", "timestamp": 1712345682.0, "data": {"tool": "navigate", "summary": "Opened youtube.com"}},
{"seq": 4, "event_type": "state_captured", "timestamp": 1712345683.0, "data": {"screenshot": "/api/tasks/a1b2c3d4/screenshots/4"}}
]
Polling pattern:
after_seq=0after_seq to the highest seq you receivedKey event types:
| Event | Meaning | Terminal? |
|---|---|---|
llm_call | Agent made an LLM API call (includes token counts) | No |
tool_executed | Agent performed an action (tap, type, navigate, etc.) | No |
state_captured | Device/browser state captured — screenshot URL in data.screenshot | No |
human_needed | Agent is stuck and needs human input | Yes |
task_completed | Task finished successfully | Yes |
task_error | Task failed with an error | Yes |
While a task is running (assigned or paused), send additional instructions:
curl -X POST https://app.letherdo.com/api/tasks/a1b2c3d4/chat \
-H "Authorization: Bearer $LETHERDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"message": "Now click on the first video result"}'
Also use this to respond when the agent emits human_needed.
After the task completes, retrieve the conversation:
curl https://app.letherdo.com/api/tasks/a1b2c3d4/chat-messages \
-H "Authorization: Bearer $LETHERDO_API_KEY"
Fetch a specific screenshot:
curl https://app.letherdo.com/api/tasks/a1b2c3d4/screenshots/1 \
-H "Authorization: Bearer $LETHERDO_API_KEY" \
--output screenshot.png
queued → assigned ↔ paused → suspended → expired
↘ completed
Resuming: Paused or suspended tasks can be resumed via POST /tasks/{id}/chat or POST /tasks/{id}/resume. The system restores the snapshot and continues.
Create recurring or one-time scheduled tasks:
# Recurring: every weekday at 9am IST
curl -X POST https://app.letherdo.com/api/schedules \
-H "Authorization: Bearer $LETHERDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"description": "Check my email inbox and summarize unread messages",
"cron_expression": "0 9 * * MON-FRI",
"timezone": "Asia/Kolkata",
"agent_type": "browser"
}'
# One-time: run at a specific time
curl -X POST https://app.letherdo.com/api/schedules \
-H "Authorization: Bearer $LETHERDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"description": "Post a tweet about our product launch",
"scheduled_at": 1712400000.0,
"timezone": "America/New_York",
"agent_type": "letherdo"
}'
Natural language parsing: Use POST /parse-schedule to convert text like "every Monday at 9am" into a cron expression or timestamp before creating the schedule.
Manual trigger: Run any schedule immediately with POST /schedules/{id}/run.
See API Reference for all schedule endpoints.
| Constraint | Free | Pro | Ultra |
|---|---|---|---|
| Messages/day | 25 | 200 | Unlimited |
| Concurrent tasks | 2 | 5 | 10 |
| Concurrent workflows | 2 | 5 | 10 |
| New tasks/day | 30 | 30 | 30 |
| Max schedules | 25 | 25 | 25 |
| Max API keys | 10 | 10 | 10 |
Additional constraints:
import os, time, requests
API = "https://app.letherdo.com/api"
KEY = os.environ["LETHERDO_API_KEY"]
H = {"Authorization": f"Bearer {KEY}", "Content-Type": "application/json"}
# 1. Create task
r = requests.post(f"{API}/tasks", json={
"description": "Go to google.com, search for 'best restaurants in Mumbai', and list the top 5 results with their names and ratings",
"agent_type": "browser"
}, headers=H)
task = r.json()
task_id = task["id"]
# 2. Poll events until done
seq = 0
while True:
time.sleep(4)
r = requests.get(f"{API}/tasks/{task_id}/events?after_seq={seq}", headers=H)
events = r.json()
for e in events:
seq = e["seq"]
if e["event_type"] in ("task_completed", "task_error", "human_needed"):
print(f"Task finished: {e['event_type']}")
break
if e["event_type"] == "llm_call":
print(f"Agent working: LLM call ({e['data'].get('input_tokens', 0)} tokens)")
else:
continue
break
# 3. Get results
r = requests.get(f"{API}/tasks/{task_id}/chat-messages", headers=H)
messages = r.json()
for m in messages:
if m["role"] == "assistant":
for part in m.get("parts", []):
if part.get("type") == "text":
print(part["text"])
# 1. Create task (may queue if no emulator free)
r = requests.post(f"{API}/tasks", json={
"description": "Open YouTube app, search for 'Claude AI demo', and tell me the title and view count of the top 3 results",
"agent_type": "letherdo"
}, headers=H)
task = r.json()
task_id = task["id"]
# 2. Wait for device assignment
while task["status"] == "queued":
time.sleep(3)
r = requests.get(f"{API}/tasks/{task_id}", headers=H)
task = r.json()
# 3. Poll events (same pattern as Example 1)
seq = 0
done = False
while not done:
time.sleep(4)
r = requests.get(f"{API}/tasks/{task_id}/events?after_seq={seq}", headers=H)
for e in r.json():
seq = e["seq"]
if e["event_type"] in ("task_completed", "task_error", "human_needed"):
done = True
break
# Parse natural language to cron
r = requests.post(f"{API}/parse-schedule", json={
"text": "every weekday at 9:30am",
"timezone": "Asia/Kolkata"
}, headers=H)
parsed = r.json()
# {"type": "recurring", "cron_expression": "30 9 * * MON-FRI", "cron_description": "At 09:30, Monday through Friday"}
# Create the schedule
r = requests.post(f"{API}/schedules", json={
"description": "Check my Gmail inbox and summarize any urgent emails",
"cron_expression": parsed["cron_expression"],
"timezone": "Asia/Kolkata",
"agent_type": "browser"
}, headers=H)
schedule = r.json()
print(f"Schedule created: {schedule['id']}, next run: {schedule['next_run_at']}")
# List all schedules
r = requests.get(f"{API}/schedules", headers=H)
for s in r.json():
print(f" {s['description']} — {'enabled' if s['enabled'] else 'disabled'}")
# Manually trigger it now
r = requests.post(f"{API}/schedules/{schedule['id']}/run", headers=H)
print(f"Manual run started: task {r.json()['task_id']}")
For complete endpoint details (all request/response schemas, status codes, and parameters), see references/api.md.
Provides behavioral guidelines to reduce common LLM coding mistakes, focusing on simplicity, surgical changes, assumption surfacing, and verifiable success criteria.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub letherdo/skill --plugin letherdo