Skip to content

Receiving Data

Task answers, replies, notification taps, and submissions can all be consumed in a few ways. Pick by how your consumer runs:

  • sp collect: bounded, machine-readable collection for scripts and agents. The recommended default.
  • SDK handles: typed inputs() / replies() streams per task or notification, plus submissions() on the client.
  • Inline wait: keep the original HTTP request open and read the first answer from the response. Simplest possible loop.
  • Event streams: sp events or the SDKs' raw feed, live or replayed.

sp collect

Pipe a send into sp collect and it prints one JSON line per answer, then a final end line with counts, so a consuming script never hangs and never parses ambiguous output:

bash
sp task -t crew --format json --title "Status?" --text-input "What are you seeing?" \
  | sp collect --until idle:5m
json
{"type":"sent","groupId":"grptsk_...","members":[{"taskId":"tsk_...","recipient":{"publicId":"usr_...","name":"Alice"}}]}
{"type":"input","taskId":"tsk_...","recipient":{"name":"Alice"},"inputType":"text","uploads":[{"kind":"text","value":"All quiet"}]}
{"type":"end","reason":"idle","counts":{"input":1},"members":{"total":1,"completed":1,"deleted":0,"pending":0}}

Stop conditions compose: --until complete, idle:<dur>, count:<n>, timeout:<dur>, or forever. See sp collect for all modes.

--submissions collects the submissions sent to you instead of a send's answers, so it needs no piped send. Submissions have no natural end: it watches forever unless you pass --until.

bash
sp collect --submissions --since 24h --until idle:10m
json
{"type":"submission","id":"sbm_...","actor":{"publicId":"usr_...","name":"Alice"},"body":{"text":"Gate is jammed"},"createdAt":"..."}
{"type":"end","reason":"idle","counts":{"submission":1}}

SDK handles

Every send returns a handle whose inputs() and replies() streams carry only that send's own events. Both take a timeout and a replay flag.

python
group = client.send_task(topic="deploys", title="Approve deploy?",
                         inputs=[ChoiceInput(options=["Approve", "Deny"])])

async for g in group.inputs(timeout=600):
    if isinstance(g.item, TaskCompleted):
        print(g.recipient, "answered", g.item.uploads)

Submissions belong to no send, so they come off the client itself. client.submissions() yields each one as it arrives, with bound download handles on its photo, voice, and file objects.

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

See Python and TypeScript for the full stream and event types, and their Submissions sections (Python, TypeScript) for the submission shape.

Inline wait

Add Wait: true to a curl send that has exactly one input, and the connection stays open until the first answer arrives, which becomes the response body. Text and choice answers arrive as the value itself; photo, voice, and file answers arrive as a presigned download URL.

bash
RESULT=$(curl -s -X POST https://api.simplepu.sh/v1/tasks \
  -H "API-Token: $SP_API_TOKEN" \
  -H "Topic: deploys" \
  -H "Choice-Input: Approve deploy?;Approve,Reject" \
  -H "Wait: true" | tr -d '\n')

The tr strips the heartbeat newlines the server emits every 30 seconds to keep proxies from dropping the socket. The CLI equivalent is sp task ... --wait, which blocks until the first completion and prints the result.

Not for long waits

A dropped HTTP connection loses the inline answer. For anything beyond quick approvals, use sp collect or an SDK. They collect over a resumable WebSocket, and --since lets a restarted collector pick up answers that arrived while it was down.

Event streams

sp events prints every event on your account, live or from history. It needs your API token ($SP_API_TOKEN).

bash
sp events                            # everything, live
sp events -t deploys --since 24h     # one topic, last 24 hours, then exit
sp events --type submission.photo    # only photo submissions
sp events --since 24h --follow       # replay, then keep streaming

--type filters range from coarse (task, submission) to fine (task.input.choice, submission.photo); the full list is in the CLI guide. In the SDKs, the same feed is client.events().

Decryption on receive

Every receive path decrypts on the fly when you supply passwords. On the CLI, -p is repeatable and uses one grammar everywhere: -p secret@alerts registers a password for topic alerts, a bare -p secret is your personal password (submissions and sends to your own devices). Events encrypted with a password you didn't supply pass through with ciphertext intact, so mixed streams work without errors.

bash
sp events -p "hunter2@family" -p "personal-secret"
sp task -t family --text-input "Site visit notes" -p "hunter2@family" --wait

In the SDKs, the same model is the passwords constructor option. See Encryption.

Downloading payloads

Photo, voice, and file answers are stored payloads, not inline bytes. Wherever you receive them you get a way to fetch: presigned URLs on the wire and in sp collect / sp events output, or bound handles with read() / save() / download_url() in the SDKs. Presigned URLs expire after about 5 minutes; mint fresh ones by re-fetching rather than storing them.