Skip to content

Receiving Data ​

There are several ways to consume task answers, replies, notification taps, and submissions. Pick the one that fits how your consumer runs:

  • sp collect: machine-readable collection with a defined end, for scripts and agents. This is 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. This is the simplest possible loop.
  • Event streams: sp events or the SDKs' raw feed, live or replayed.

sp collect ​

Pipe a send into sp collect. It prints one JSON line per answer, then a final end line with counts. A consuming script therefore never hangs and never has to parse 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_...","instances":[{"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},"instances":{"total":1,"completed":1,"deleted":0,"canceled":0,"declined":0,"expired":0,"pending":0}}

Stop conditions can be combined: --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 the answers to a send, so it needs no piped send. Submissions have no natural end. The command 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. Its inputs() and replies() streams carry only the events of that send. 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 you read them from the client itself. client.submissions() yields each one as it arrives. Its photo, voice, and file objects are bound download handles.

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. The connection then stays open until the first answer arrives, and that answer 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 server emits a heartbeat newline every 30 seconds so that proxies do not drop the connection. The tr strips those newlines. 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 can be coarse (task, submission) or fine-grained (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 the same format everywhere. -p secret@alerts registers a password for the topic alerts. A bare -p secret is your personal password, used for submissions and sends to your own devices. Events encrypted with a password you did not supply pass through with their 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 passwords go into the passwords constructor option. See Encryption.

Downloading payloads ​

Photo, voice, and file answers are stored on the server, not included as bytes. Wherever you receive them, you get a way to fetch them. On the wire and in sp collect / sp events output that is a presigned URL. In the SDKs it is a bound handle with read() / save() / download_url(). Presigned URLs expire after about 5 minutes. Fetch a fresh one when you need it rather than storing it.