Skip to content

Getting Started

Simplepush connects phones with scripts, agents, and workflows, in both directions. Send a question to a phone and get the answer back as structured data; or send text, photos, voice notes and files from the phone into anything that can consume an event stream.

This guide covers personal use, where everything runs off your own account. Teams can also run Simplepush as an organization: a shared namespace with managed members, admin-controlled topics, and an org API key for scripts and agents.

Download the app

There's no registration, no signup form, no account to manage. Download the app and you're ready.

Download on the App StoreGet it on Google Play

Your API token

The app generates an API token for your account; you'll find it in the app settings under API Token. It authenticates everything you send from the command line, a script, or an agent. Export it once:

bash
export SP_API_TOKEN="your-token"

The CLI and the curl examples below read $SP_API_TOKEN; the SDKs take the token as a constructor argument.

Organizations authenticate differently: scripts use the org API key (Api-Key header, OrgClient in the SDKs) and the CLI uses an sp auth login session. See Organizations.

Install

bash
npm install -g @simplepush/cli
bash
pip install simplepush
bash
npm install @simplepush/sdk

Tasks

Send a task with no target: it lands on your own devices with a push notification, then the answer comes back to your code when you tap it on the phone.

A task rendered in the Simplepush app
bash
# --wait keeps the command running until you answer, then prints it
sp task --title "Deploy v2.1.0?" -c "Approve,Deny" --wait
# ...tap Approve on your phone...
# Approve
bash
# Wait: true holds the connection open until you answer; the body is the answer
curl -X POST https://api.simplepu.sh/v1/tasks \
  -H "API-Token: $SP_API_TOKEN" \
  -H "Title: Deploy v2.1.0?" \
  -H "Choice-Input: Approve,Deny" \
  -H "Wait: true"
python
import asyncio
from simplepush import Client, ChoiceInput, TaskCompleted

client = Client(api_token="YOUR_API_TOKEN")

task = client.send_task(
    title="Deploy v2.1.0?",
    inputs=[ChoiceInput(options=["Approve", "Deny"])],
)

async def main():
    async for event in task.inputs(timeout=600):
        if isinstance(event, TaskCompleted):
            print(event.uploads)  # [ChoiceUpload(index=0, value="Approve")]

asyncio.run(main())
typescript
import { Client } from "@simplepush/sdk";

const client = new Client({ apiToken: "YOUR_API_TOKEN" });

const task = await client.sendTask({
  title: "Deploy v2.1.0?",
  inputs: [{ type: "choice", options: ["Approve", "Deny"], required: true }],
});

for await (const item of task.inputs({ idleMs: 600_000 })) {
  if (item.kind === "taskCompleted") console.log(item.uploads);
}

That's the whole loop: your code asks, a person answers, the answer comes back to your code. Everything else builds on it, richer inputs (sliders, action buttons, photo and voice answers), more recipients, and streaming collection instead of a blocking wait. See Tasks.

Files in, files out

Tasks carry files in both directions: attach a file to the send, and ask for a photo, voice recording, or file back. Uploaded answers come back as downloads: read() / save() handles in the SDKs, saved to disk with --save-files on the CLI, a presigned URL on curl.

bash
sp task --title "Inspect the meter" -f ./manual.pdf \
  --photo-input "Photo of the meter" --format json \
  | sp collect --inputs --save-files ./inbox --until complete
bash
# One file rides as the request body; Wait: true returns the photo
# answer as a presigned download URL
curl -X POST https://api.simplepu.sh/v1/tasks \
  -H "API-Token: $SP_API_TOKEN" \
  -H "Title: Inspect the meter" \
  -H "Attachment: manual.pdf" \
  -H "Photo-Input: Photo of the meter" \
  -H "Wait: true" \
  --data-binary @./manual.pdf
python
import asyncio
from simplepush import Client, PhotoInput, TaskCompleted, PhotoUpload

client = Client(api_token="YOUR_API_TOKEN")

task = client.send_task(
    title="Inspect the meter",
    files=["./manual.pdf"],
    inputs=[PhotoInput(description="Photo of the meter")],
)

async def main():
    async for event in task.inputs(timeout=600):
        if isinstance(event, TaskCompleted):
            for u in event.uploads:
                if isinstance(u, PhotoUpload):
                    print(await u.save("./inbox"))

asyncio.run(main())
typescript
import { readFileSync } from "node:fs";
import { Client } from "@simplepush/sdk";

const client = new Client({ apiToken: "YOUR_API_TOKEN" });

const task = await client.sendTask({
  title: "Inspect the meter",
  files: [{ filename: "manual.pdf", data: readFileSync("./manual.pdf") }],
  inputs: [{ type: "photo", description: "Photo of the meter", required: true }],
});

for await (const item of task.inputs({ idleMs: 600_000 })) {
  if (item.kind === "taskCompleted") {
    for (const u of item.uploads) {
      if (u.kind === "photo") console.log(await u.save("./inbox"));
    }
  }
}

Topics

A topic is a named bucket of devices; sending to a topic reaches everyone subscribed to it. Topics are how you go from "ping myself" to "ask my family" or "ask the whole crew". Anyone who knows a topic's value can join it, so treat the value like a capability, and use end-to-end encryption or write-protection when it matters. See Topics.

Submissions

Submissions work in the opposite direction: open the app, and send text, a photo, a voice memo, your location, or a file, with nobody asking first. A listening script receives it within seconds.

The submission composer in the Simplepush app
bash
sp collect --submissions --until count:1
python
import asyncio
from simplepush import Client

client = Client(api_token="YOUR_API_TOKEN")

async def main():
    async for s in client.submissions():
        if s.body:
            print("text:", s.body.text)
        if s.photo:
            await s.photo.save("./inbox")

asyncio.run(main())
typescript
import { Client } from "@simplepush/sdk";

const client = new Client({ apiToken: "YOUR_API_TOKEN" });

for await (const s of client.submissions()) {
  if (s.body) console.log("text:", s.body.text);
  if (s.photo) await s.photo.save("./inbox");
}

See Submissions.

Notifications

A notification is fire-and-forget: it pops up on your devices, and once dismissed nothing is saved. It is the right call for an alert or a heads-up, where a task would be overkill.

A Simplepush notification banner on iOS with choice buttons
bash
sp notify --title "Reminder" --content "Standup in 5 minutes"
bash
curl -X POST https://api.simplepu.sh/v1/notifications \
  -H "API-Token: $SP_API_TOKEN" \
  -H "Title: Reminder" \
  -H "Content: Standup in 5 minutes"
python
from simplepush import Client

client = Client(api_token="YOUR_API_TOKEN")
client.send_notification(title="Reminder", content="Standup in 5 minutes")
typescript
import { Client } from "@simplepush/sdk";

const client = new Client({ apiToken: "YOUR_API_TOKEN" });
await client.sendNotification({ title: "Reminder", content: "Standup in 5 minutes" });

Notifications can also carry an image, a single input, or action buttons, and go to topics and teams just like tasks. See Notifications.

Next steps