Skip to content

Tasks

Task

A task is delivered as a push notification and shown as a card in the app, where it stays until handled. It carries a title, content, attachments, and any number of inputs: the questions the recipient answers with a tap, text, a number, a photo, a voice recording, a file, or their location. Every answer comes back to you as structured data; see Receiving Data.

Free accounts can put up to 5 inputs on a task, paying accounts up to 50. Uploaded answers (photos, voice, files) count against your storage pool.

API endpoint

POST /v1/tasks, authenticated with API-Token (personal) or Api-Key (org). See the cURL guide for header-based sends; POST /v1/tasks/json is the JSON-body twin.

Delivery modes: independent (default) and shared

Sending to more than one recipient creates an independent task per recipient by default. Each instance has its own id, its own answers, and its own append token, tied together in a group: the send returns a grptsk_ group id with one tsk_ instance per recipient. That's what lets you ask twelve people the same question and know exactly who answered what.

Shared mode (shared=True / --shared / Shared: true header) is the alternative: one task all recipients see and answer together, completed once. Use it when you want a single answer from whoever gets there first, rather than one per person.

Inputs

Every input takes an optional description shown to the recipient and a required flag (default true) that controls whether the task can complete without it. By default a task auto-commits: each answer is delivered as soon as the recipient fills it in, and the task completes when all required inputs are done. Disable auto-commit (--submit on the CLI, Auto-Commit: false on curl, auto_commit=False / autoCommit: false in the SDKs) to let recipients review and change answers until they explicitly submit.

Text

A free-form text field, delivered back as a UTF-8 string.

A task with a text input
bash
sp task -t standup --text-input "How was the meeting?"
bash
curl -X POST https://api.simplepu.sh/v1/tasks \
  -H "API-Token: $SP_API_TOKEN" \
  -H "Topic: standup" \
  -H "Text-Input: How was the meeting?"
python
from simplepush import Client, TextInput

client = Client(api_token="YOUR_API_TOKEN")
client.send_task(topic="standup", inputs=[TextInput(description="How was the meeting?")])
typescript
import { Client } from "@simplepush/sdk";

const client = new Client({ apiToken: "YOUR_API_TOKEN" });
await client.sendTask({
  topic: "standup",
  inputs: [{ type: "text", description: "How was the meeting?", required: true }],
});

Choice

A list of predefined options; the answer is the selected option and its index.

A task with a choice input
bash
sp task -t deploys -c "Deploy v2.1.0 to production?;Approve,Reject,Delay"
bash
curl -X POST https://api.simplepu.sh/v1/tasks \
  -H "API-Token: $SP_API_TOKEN" \
  -H "Topic: deploys" \
  -H "Choice-Input: Deploy v2.1.0 to production?;Approve,Reject,Delay"
python
from simplepush import Client, ChoiceInput

client = Client(api_token="YOUR_API_TOKEN")
client.send_task(topic="deploys", inputs=[ChoiceInput(
    description="Deploy v2.1.0 to production?",
    options=["Approve", "Reject", "Delay"],
)])
typescript
import { Client } from "@simplepush/sdk";

const client = new Client({ apiToken: "YOUR_API_TOKEN" });
await client.sendTask({
  topic: "deploys",
  inputs: [{
    type: "choice",
    description: "Deploy v2.1.0 to production?",
    options: ["Approve", "Reject", "Delay"],
    required: true,
  }],
});

Set multi=True (CLI: ;multi=true) to allow multiple selections, optionally bounded with min_selections / max_selections. A multi-choice never renders as a tappable actionable notification; the recipient answers in the app, and an optional multi-choice may be answered with no selection at all.

bash
sp task -t deploys \
  -c "Which services need the hotfix?;api,worker,scheduler,web;multi=true;minSelections=1;maxSelections=3"
bash
# The Choice-Input header is single-select only; multi goes through the JSON endpoint.
curl -X POST https://api.simplepu.sh/v1/tasks/json \
  -H "API-Token: $SP_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "topic": "deploys",
    "inputs": [{
      "type": "choice",
      "description": "Which services need the hotfix?",
      "options": ["api", "worker", "scheduler", "web"],
      "multi": true,
      "minSelections": 1,
      "maxSelections": 3
    }]
  }'
python
from simplepush import Client, ChoiceInput

client = Client(api_token="YOUR_API_TOKEN")
client.send_task(topic="deploys", inputs=[ChoiceInput(
    description="Which services need the hotfix?",
    options=["api", "worker", "scheduler", "web"],
    multi=True,
    min_selections=1,
    max_selections=3,
)])
typescript
import { Client } from "@simplepush/sdk";

const client = new Client({ apiToken: "YOUR_API_TOKEN" });
await client.sendTask({
  topic: "deploys",
  inputs: [{
    type: "choice",
    description: "Which services need the hotfix?",
    options: ["api", "worker", "scheduler", "web"],
    multi: true,
    minSelections: 1,
    maxSelections: 3,
    required: true,
  }],
});

Actions

Buttons the recipient taps, for example Accept and Deny. Each action has a stable key that is reported back to you, a label shown on the button, and an optional style (default, primary, or destructive).

A task with Approve and Deny action buttons
bash
sp task -t deploys --content "Deploy to prod?" \
  -a "approve=Approve:primary,deny=Deny:destructive"
bash
curl -X POST https://api.simplepu.sh/v1/tasks \
  -H "API-Token: $SP_API_TOKEN" \
  -H "Topic: deploys" \
  -H "Content: Deploy to prod?" \
  -H "Action-Input: approve=Approve:primary,deny=Deny:destructive"
python
from simplepush import Client, ActionsInput, Action

client = Client(api_token="YOUR_API_TOKEN")
client.send_task(topic="deploys", content="Deploy to prod?", inputs=[ActionsInput(actions=[
    Action(key="approve", label="Approve", style="primary"),
    Action(key="deny", label="Deny", style="destructive"),
])])
typescript
import { Client } from "@simplepush/sdk";

const client = new Client({ apiToken: "YOUR_API_TOKEN" });
await client.sendTask({
  topic: "deploys",
  content: "Deploy to prod?",
  inputs: [{
    type: "actions",
    required: true,
    actions: [
      { key: "approve", label: "Approve", style: "primary" },
      { key: "deny", label: "Deny", style: "destructive" },
    ],
  }],
});

Slider

The recipient picks a number on a [min, max] scale, for example a pool inspector logging pH on a 0 to 14 scale. min and max are required; step, unit, and a default position are optional. On an encrypted send the server learns nothing about the scale.

A task with a slider input
bash
sp task -t pools --content "Log the readings" \
  -s "pH of pool 3;min=0;max=14;step=0.1;unit=pH;default=7"
bash
curl -X POST https://api.simplepu.sh/v1/tasks \
  -H "API-Token: $SP_API_TOKEN" \
  -H "Topic: pools" \
  -H "Content: Log the readings" \
  -H "Slider-Input: pH of pool 3;min=0;max=14;step=0.1;unit=pH;default=7"
python
from simplepush import Client, SliderInput

client = Client(api_token="YOUR_API_TOKEN")
client.send_task(topic="pools", content="Log the readings", inputs=[
    SliderInput(description="pH of pool 3", min=0, max=14, step=0.1, unit="pH", default_value=7),
])
typescript
import { Client } from "@simplepush/sdk";

const client = new Client({ apiToken: "YOUR_API_TOKEN" });
await client.sendTask({
  topic: "pools",
  content: "Log the readings",
  inputs: [{
    type: "slider",
    description: "pH of pool 3",
    min: 0, max: 14, step: 0.1, unit: "pH", defaultValue: 7,
    required: true,
  }],
});

Photo, voice, file

Prompt the recipient to take a picture, record their voice, or pick a file. The payload is uploaded from their device and delivered to you as a download you can fetch (a presigned URL, or read()/save() in the SDKs).

A task with photo, voice recording, and file inputs
bash
sp task -t site-crew \
  --photo-input "Photo of the finished junction box" \
  --voice-recording-input "Describe the damage" \
  --file-input "Upload the signed contract"
bash
curl -X POST https://api.simplepu.sh/v1/tasks \
  -H "API-Token: $SP_API_TOKEN" \
  -H "Topic: site-crew" \
  -H "Photo-Input: Photo of the finished junction box" \
  -H "Voice-Recording-Input: Describe the damage" \
  -H "File-Input: Upload the signed contract"
python
from simplepush import Client, PhotoInput, VoiceRecordingInput, FileUploadInput

client = Client(api_token="YOUR_API_TOKEN")
client.send_task(topic="site-crew", inputs=[
    PhotoInput(description="Photo of the finished junction box"),
    VoiceRecordingInput(description="Describe the damage"),
    FileUploadInput(description="Upload the signed contract"),
])
typescript
import { Client } from "@simplepush/sdk";

const client = new Client({ apiToken: "YOUR_API_TOKEN" });
await client.sendTask({
  topic: "site-crew",
  inputs: [
    { type: "photo", description: "Photo of the finished junction box", required: true },
    { type: "voiceRecording", description: "Describe the damage", required: true },
    { type: "file", description: "Upload the signed contract", required: true },
  ],
});

Location

The recipient shares their GPS position from the app; the answer carries coordinates plus accuracy, altitude, heading, speed, and a timestamp when available.

A task with a location input
bash
sp task -t field --location-input "Share your current position"
bash
curl -X POST https://api.simplepu.sh/v1/tasks \
  -H "API-Token: $SP_API_TOKEN" \
  -H "Topic: field" \
  -H "Location-Input: Share your current position"
python
from simplepush import Client, LocationInput

client = Client(api_token="YOUR_API_TOKEN")
client.send_task(topic="field", inputs=[LocationInput(description="Share your current position")])
typescript
import { Client } from "@simplepush/sdk";

const client = new Client({ apiToken: "YOUR_API_TOKEN" });
await client.sendTask({
  topic: "field",
  inputs: [{ type: "location", description: "Share your current position", required: true }],
});

Reply composer

Independent of inputs, a task can carry a reply composer: an open channel where recipients write back with text, photos, files, audio, or location, like a message thread hanging off the task. Modes: one-shot (one reply closes it), sticky (stays open), one-time-per-user (one reply per recipient). Collect replies with sp collect --replies or the handles' replies() streams.

A task with a reply composer below it
bash
sp task -t oncall --title "Incident 4312 resolved" \
  --content "Reply here if it reopens." \
  --reply sticky
bash
curl -X POST https://api.simplepu.sh/v1/tasks \
  -H "API-Token: $SP_API_TOKEN" \
  -H "Topic: oncall" \
  -H "Title: Incident 4312 resolved" \
  -H "Content: Reply here if it reopens." \
  -H "Reply: sticky"
python
from simplepush import Client, ReplyMode

client = Client(api_token="YOUR_API_TOKEN")
client.send_task(
    topic="oncall",
    title="Incident 4312 resolved",
    content="Reply here if it reopens.",
    reply=ReplyMode.STICKY,
)
typescript
import { Client } from "@simplepush/sdk";

const client = new Client({ apiToken: "YOUR_API_TOKEN" });
await client.sendTask({
  topic: "oncall",
  title: "Incident 4312 resolved",
  content: "Reply here if it reopens.",
  reply: "sticky",
});

Attachments

A task can carry link attachments (URLs, delivered as-is) and file attachments (uploaded with the send, downloaded by the recipient, encrypted when the send is encrypted).

A task with a file attachment
bash
sp task --title "Review these" \
  --content "Slides attached, report linked." \
  -l https://example.com/report.pdf \
  -f ./slides.pdf
bash
# Link attachments via the Attachment header; one file as the request body
curl -X POST https://api.simplepu.sh/v1/tasks \
  -H "API-Token: $SP_API_TOKEN" \
  -H "Title: Signed contract" \
  -H "Content: Signed copy attached." \
  -H "Attachment: contract.pdf" \
  --data-binary @./contract.pdf
python
client.send_task(title="Review these",
                 content="Slides attached, report linked.",
                 links=["https://example.com/report.pdf"],
                 files=["./slides.pdf"])
typescript
// files are FileAttachment objects: { filename, data: Uint8Array, contentType? }
await client.sendTask({
  title: "Review these",
  content: "Slides attached, report linked.",
  links: ["https://example.com/report.pdf"],
  files: [{ filename: "slides.pdf", data: bytes }],
});

In the Attachment header, entries starting with http(s):// are links; any other entry names the file in the request body.

Markdown

Pass --markdown (CLI) or content_format="markdown" / contentFormat: 'markdown' (SDKs) to have the recipient render content as Markdown. The format marker is never encrypted.

A task with Markdown content: bold text, a bullet list, and inline code
bash
sp task --title "Deploy summary" --markdown --content "Canary error rate **0.02%** over 30 min.

- **214 checks** passed
- migrations: \`V42__topics.sql\`
- rollback target: \`v2.0.9\`"
bash
# The header path has no markdown flag; use the JSON endpoint.
curl -X POST https://api.simplepu.sh/v1/tasks/json \
  -H "API-Token: $SP_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Deploy summary",
    "content": "Canary error rate **0.02%** over 30 min.\n\n- **214 checks** passed\n- migrations: `V42__topics.sql`\n- rollback target: `v2.0.9`",
    "contentFormat": "markdown"
  }'
python
from simplepush import Client, ContentFormat

client = Client(api_token="YOUR_API_TOKEN")
client.send_task(
    title="Deploy summary",
    content=(
        "Canary error rate **0.02%** over 30 min.\n\n"
        "- **214 checks** passed\n"
        "- migrations: `V42__topics.sql`\n"
        "- rollback target: `v2.0.9`"
    ),
    content_format=ContentFormat.MARKDOWN,
)
typescript
import { Client } from "@simplepush/sdk";

const client = new Client({ apiToken: "YOUR_API_TOKEN" });
await client.sendTask({
  title: "Deploy summary",
  content: [
    "Canary error rate **0.02%** over 30 min.",
    "",
    "- **214 checks** passed",
    "- migrations: `V42__topics.sql`",
    "- rollback target: `v2.0.9`",
  ].join("\n"),
  contentFormat: "markdown",
});

Send to your own devices

Omit the topic and the task goes to your own devices: a reminder, a checklist, or an agent asking you to approve something it's about to do. It returns a single task, and it's encrypted automatically under your personal password when your client has one configured.

Your API token is in the app settings under API Token. The CLI reads it from $SP_API_TOKEN or the --api-token flag; see Credentials.

bash
export SP_API_TOKEN=YOUR_API_TOKEN
bash
sp task --title "Reminder" --content "Water the plants"
bash
curl -X POST https://api.simplepu.sh/v1/tasks \
  -H "API-Token: $SP_API_TOKEN" \
  -H "Title: Reminder" \
  -H "Content: Water the plants"
python
client = Client(api_token="YOUR_API_TOKEN", passwords="personal-secret")
client.send_task(title="Reminder", content="Water the plants")
typescript
import { Client } from "@simplepush/sdk";

const client = new Client({ apiToken: "YOUR_API_TOKEN", passwords: "personal-secret" });
await client.sendTask({ title: "Reminder", content: "Water the plants" });

Follow-ups: subtasks

An existing task can grow: append a subtask with new content and inputs using the append token from the send. Subtasks inherit the parent's recipients and encryption and go one level deep. See sp subtask.

A task with an appended subtask carrying a choice input
bash
sp task -t deploys --title "Deploying v2.1.1" --content "Canary at 5%."
# → info: append token: at_...
sp subtask --append-token at_... \
  --content "Canary is clean." \
  -c "Promote to 100%?;Promote,Halt"
bash
# A personal append authenticates with API-Token plus the append token.
curl -X POST https://api.simplepu.sh/v1/subtasks \
  -H "API-Token: $SP_API_TOKEN" \
  -H "Append-Token: at_..." \
  -H "Content: Canary is clean." \
  -H "Choice-Input: Promote to 100%?;Promote,Halt"
python
from simplepush import Client, ChoiceInput

client = Client(api_token="YOUR_API_TOKEN")
task = client.send_task(topic="deploys", title="Deploying v2.1.1", content="Canary at 5%.")
task.append(content="Canary is clean.",
            inputs=[ChoiceInput(description="Promote to 100%?", options=["Promote", "Halt"])])
typescript
import { Client } from "@simplepush/sdk";

const client = new Client({ apiToken: "YOUR_API_TOKEN" });
const task = await client.sendTask({
  topic: "deploys",
  title: "Deploying v2.1.1",
  content: "Canary at 5%.",
});
await task.append({
  content: "Canary is clean.",
  inputs: [{ type: "choice", description: "Promote to 100%?", options: ["Promote", "Halt"], required: true }],
});

Encryption

Topic sends encrypt end-to-end when a password is set for the topic; titles, content, input descriptions, options, labels, slider scales, links, and file bytes are all sealed before leaving your machine. See Encryption.