From your agent to your phone, and back.

Your agent makes one request. Kabu stores the message, pushes it to your iPhone and holds the agent's call open, then hands your answer back to the agent that asked.

acme-api · claude-code
$ kabu send "Tests passed" "412 passed, 0 failed"
{"ok": true, "id": "msg_01K8ZB4TQ2N6X9M3C7D5F8H1JW", "status": "sent", "attachments": 0}
$ kabu ask "Deploy v1.4.2 to prod?" "Canary green for 30 min"
# waiting for your answer…
{"choices": ["approve"], "decision": "approve", "id": "msg_01K8ZD6N2Q9V4T8X1M5R7W3PHC", "option": "approve", "outcome": "answered", "status": "answered", "text": null, "values": null, "via": "lock_screen"}
# exit 0 · deploying v1.4.2…

Signed receipt · ES256 · verify with JWKS

An agent tells you something, then asks; your answer goes back. The terminal runs kabu send "Tests passed", and a pigeon carries it to the phone's Lock Screen. Then it runs kabu ask "Deploy v1.4.2 to prod?"; the question lands above the first notification with Approve and Reject. Approve is tapped, the pigeon carries the answer back, and the terminal prints decision approve, exit 0, with a signed receipt.

Your agent makes one request.

The CLI, the plugin, MCP and the HTTP API all create the same message, with POST /v2/messages. Kabu commits it, with its deadline, before it answers.

Learn how
The same ask, from
kabu ask "Deploy v1.4.2 to prod?" "Canary green for 30 min"
curl -sS "https://usekabu.com/api/v2/messages?wait=25" \
  -H "Authorization: Bearer $KABU_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"title": "Deploy v1.4.2 to prod?", "body": "Canary green for 30 min", "project": "acme-api", "ask": {"type": "confirm"}, "expires_in": 300}'
{"name": "ask_human", "arguments": {"project": "acme-api", "agent": "claude-code", "title": "Deploy v1.4.2 to prod?", "message": "Canary green for 30 min", "ask": {"type": "confirm"}}}

Kabu pushes it to your iPhone.

An update arrives as a notification. An ask is marked time-sensitive, so it can come through Focus, with its own sound and the buttons to answer it, unless your quiet hours say otherwise.

Learn how

You answer where it reaches you.

From the notification, the app or an answer page. The first answer settles the ask; a second one is refused.

Learn how

The agent gets your answer.

kabu ask holds its request open with ?wait= until you answer, then prints your answer as JSON and exits with a code the agent branches on.

Learn how
acme-api — zsh
$ kabu ask "Deploy v1.4.2 to prod?" "Canary green for 30 min"
{"choices": ["approve"], "decision": "approve", "id": "msg_01K8ZD6N2Q9V4T8X1M5R7W3PHC", "option": "approve", "outcome": "answered", "status": "answered", "text": null, "values": null, "via": "lock_screen"}
# exit 0: approved, so the deploy goes ahead (a reject exits 10)

Sent once, even when retried.

kabu sends an Idempotency-Key with every create, the same on each retry, so a retry after a dropped connection returns the same message, never a second notification. Over HTTP, send one yourself.

Learn how
The same request, sent again after a timeout
POST /api/v2/messages HTTP/1.1
Idempotency-Key: 6f1c9e2a-3b7d-4e8f-9a0b-5c2d7e4f8a1b

HTTP/1.1 201 Created
Idempotent-Replayed: true

{"object": "message", "id": "msg_01K8ZD6N2Q9V4T8X1M5R7W3PHC", "status": "pending", …}

Every change is an event.

Follow messages, answers and sessions as events: stream them and pick up where you left off, or have Kabu post them to your service as signed webhooks.

Learn how
Every event as it happens; resume with Last-Event-ID
curl -N https://usekabu.com/api/v2/events/stream \
  -H "Authorization: Bearer $KABU_API_KEY"

Put your agents on your phone.

One line installs the kabu CLI and connects this computer. Approve its code in the Kabu app, and your agents can tell you what they are doing, and ask.

mkdir -p ~/.local/bin && curl -fsSL https://usekabu.com/kabu -o ~/.local/bin/kabu && chmod +x ~/.local/bin/kabu && export PATH="$HOME/.local/bin:$PATH" && kabu login --device