Skip to content

cURL

Everything the CLI and SDKs do goes over a plain HTTP API, so curl alone is enough for scripts and quick experiments. Sends use simple request headers; if you prefer JSON bodies, every endpoint has a /json twin that takes the same fields as a JSON object.

Authentication

Every request needs a credential. There are no anonymous sends.

  • Personal: your API token (app settings, under API Token) in an API-Token header.
  • Organization: the org API key in an Api-Key header.

Send a notification

bash
curl -i -X POST https://api.simplepu.sh/v1/notifications \
  -H "API-Token: YOUR_API_TOKEN" \
  -H "Topic: builds" \
  -H "Title: Build succeeded" \
  -H "Content: main @ abc123 deployed in 47s"

The response carries the id in the X-Notification-Id header: a grpntf_ group id by default (each recipient gets their own instance), or a single ntf_ id with Shared: true.

Response headers

The ids and tokens come back as response headers, and curl does not print those by default. Add -i to see them above the body. To capture a single header in a script, use curl -s -o /dev/null -w '%header{x-notification-id}' ... (curl 7.83 or newer).

Omit the Topic header entirely to send to your own devices: the notification goes to every device on your own account.

Send a task

bash
curl -i -X POST https://api.simplepu.sh/v1/tasks \
  -H "API-Token: YOUR_API_TOKEN" \
  -H "Topic: deploys" \
  -H "Title: Approve deploy?" \
  -H "Choice-Input: Production deploy of v2.1.0;Approve,Reject"

Add one input header to ask for an answer:

HeaderValueNotes
Text-InputdescriptionFree-form text reply.
Choice-Inputdescription;opt1,opt2One-of-many. Description part optional.
Action-Inputdescription;key=Label:style,...Tap buttons. Description and :style optional.
Slider-Inputdescription;min=0;max=14;step=0.1;unit=pH;default=7Only min= and max= required.
Photo-InputdescriptionPhoto from the camera. Description optional.
Voice-Recording-InputdescriptionVoice recording. Description optional.
File-InputdescriptionArbitrary file upload. Description optional.
Location-InputdescriptionGPS location. Description optional.

Optional modifiers:

HeaderEffect
TagLabel for receiver-side filtering.
Critical: trueiOS critical alert (bypasses silent switch and Do Not Disturb).
Auto-Commit: falseRequire explicit completion instead of committing once all inputs are filled.
ReplyAttach a reply composer. One of one-shot, sticky, one-time-per-user (see below).

Reply modes:

  • one-shot — the first reply wins and closes the composer for everyone.
  • sticky — any device may reply any number of times.
  • one-time-per-user — each user may reply once per round; others can still reply until they've each had a turn.

The response carries X-Task-Id (grptsk_ group by default, tsk_ with Shared: true) and X-Append-Token for appending subtasks later.

Attach a file

Send the raw bytes as the request body, with Attachment naming the file. URLs in Attachment become link attachments instead:

bash
curl -X POST https://api.simplepu.sh/v1/tasks \
  -H "API-Token: YOUR_API_TOKEN" \
  -H "Topic: deploys" \
  -H "Title: Signed contract" \
  -H "Attachment: contract.pdf" \
  --data-binary @./contract.pdf

Wait for the answer inline

Add Wait: true to a send with exactly one input and the connection stays open until the first recipient answers; the answer is the response body. The server emits a heartbeat newline every 30 seconds so proxies don't drop the connection, so strip newlines before use:

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

echo "User chose: $RESULT"

For file, photo, and voice answers the body is a presigned download URL. Capture it, then curl the URL to fetch the file. The URL is self-authenticating (no API-Token header) and valid for about 5 minutes:

bash
URL=$(curl -s -X POST https://api.simplepu.sh/v1/tasks \
  -H "API-Token: YOUR_API_TOKEN" \
  -H "File-Input: Upload the file" \
  -H "Wait: true" | tr -d '\n')

curl -s "$URL" -o upload.bin

Long waits

An open HTTP connection can still be dropped by NATs and load balancers on very long waits, and on a drop the answer is lost to that request. For anything beyond quick approvals, use sp collect or an SDK: they collect over a resumable WebSocket.

Append a subtask

Use the X-Append-Token from the original send:

bash
curl -i -X POST https://api.simplepu.sh/v1/subtasks \
  -H "API-Token: YOUR_API_TOKEN" \
  -H "Append-Token: $TOKEN" \
  -H "Content: One more approval needed" \
  -H "Choice-Input: Confirm the rollback;Yes,No"

The response carries X-Subtask-Id. For a default send it is the grptsk_ group id, since the subtask is appended to every recipient's own copy of the task. For a Shared: true send it is the sub_ id of the single appended subtask.

Organization sends

Address a member by name or every member at once. Both require the org API key:

bash
curl -X POST https://api.simplepu.sh/v1/notifications \
  -H "Api-Key: YOUR_ORG_API_KEY" \
  -H "Member: Alice" \
  -H "Title: Heads up" \
  -H "Content: Gate 4 is blocked"

curl -X POST https://api.simplepu.sh/v1/notifications \
  -H "Api-Key: YOUR_ORG_API_KEY" \
  -H "Broadcast: true" \
  -H "Title: All hands" \
  -H "Content: Meeting at 3pm, link in your inbox"

Errors

Errors come back as { "error": "<code>", "msg": "..." }. The ones worth handling in scripts:

CodeStatusMeaning
authorization_error401Missing or wrong credential.
not_topic_holder403Personal send to a shared topic you don't hold.
write_protection_token_required401The topic is write-protected; send Topic-Auth-Token.
request_limit_exceeded429Daily request allowance used up.
upload_quota_exceeded413File too large or storage pool full.