Tasks

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.

sp task -t standup --text-input "How was the meeting?"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?"from simplepush import Client, TextInput
client = Client(api_token="YOUR_API_TOKEN")
client.send_task(topic="standup", inputs=[TextInput(description="How was the meeting?")])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.

sp task -t deploys -c "Deploy v2.1.0 to production?;Approve,Reject,Delay"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"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"],
)])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.
sp task -t deploys \
-c "Which services need the hotfix?;api,worker,scheduler,web;multi=true;minSelections=1;maxSelections=3"# 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
}]
}'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,
)])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).

sp task -t deploys --content "Deploy to prod?" \
-a "approve=Approve:primary,deny=Deny:destructive"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"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"),
])])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.

sp task -t pools --content "Log the readings" \
-s "pH of pool 3;min=0;max=14;step=0.1;unit=pH;default=7"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"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),
])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).

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"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"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"),
])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.

sp task -t field --location-input "Share your current position"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"from simplepush import Client, LocationInput
client = Client(api_token="YOUR_API_TOKEN")
client.send_task(topic="field", inputs=[LocationInput(description="Share your current position")])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.

sp task -t oncall --title "Incident 4312 resolved" \
--content "Reply here if it reopens." \
--reply stickycurl -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"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,
)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).

sp task --title "Review these" \
--content "Slides attached, report linked." \
-l https://example.com/report.pdf \
-f ./slides.pdf# 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.pdfclient.send_task(title="Review these",
content="Slides attached, report linked.",
links=["https://example.com/report.pdf"],
files=["./slides.pdf"])// 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.

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\`"# 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"
}'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,
)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.
export SP_API_TOKEN=YOUR_API_TOKENsp task --title "Reminder" --content "Water the plants"curl -X POST https://api.simplepu.sh/v1/tasks \
-H "API-Token: $SP_API_TOKEN" \
-H "Title: Reminder" \
-H "Content: Water the plants"client = Client(api_token="YOUR_API_TOKEN", passwords="personal-secret")
client.send_task(title="Reminder", content="Water the plants")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.

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"# 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"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"])])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.