Skip to the page

SDKs and tools

CLI

The bluebot command sends and reads texts, checks your Mac, manages webhooks and forwards events to a handler on your laptop with no tunnel. Every command, flag, setting and exit code.

The BlueBot CLI is the bluebot command. It does from a terminal what the dashboard and the API do: send a text, read your texts, check that your Mac is ready, manage webhooks and stops. Its most useful command is bluebot listen, which forwards your account’s events to a webhook handler running on your own machine, signed the same way as a real delivery, so you can build the handler without a tunnel.

Install

The package is @usebluebot/cli; the command it installs is bluebot. It needs Node 18 or newer.

Terminal
npm install --global @usebluebot/cli
bluebot --version

To run it once without installing, use npx @usebluebot/cli <command>. The plain npm name bluebot belongs to somebody else, so install the scoped package.

Sign in

Terminal
bluebot login

login asks for an API key and checks it with GET /v1/me. Make the key in the dashboard under Developers → API keys, named “CLI”; tick every scope to use every command, or only the ones you need (listen needs messages:read, the webhooks commands need webhooks:manage, status and whoami need account:read). The key is typed hidden and stored in your system’s keychain: the macOS Keychain, Windows Credential Manager, or the Secret Service on Linux. Where there’s no keychain, such as a container, it goes in a credentials.json file in the config folder that only you can read (mode 0600).

When your terminal shows colour, login greets you with BlueBot drawn in dots. With --json, NO_COLOR or no terminal, it prints plain text.

FlagWhat it does
--stdinRead the key from standard input instead of asking, for scripts: printf %s "$KEY" | bluebot login --stdin.
--profile <name>Store it under this profile. Default default.

Use profiles for more than one account or key, such as --profile work. bluebot logout forgets a profile’s key (--all forgets every one); revoke the key itself in the dashboard.

Terminal
bluebot whoami
Text
Signed in to maria@sparkleclean.com (acct_1Hq2LxW9cVbN3mKpRtYs0a)
Key       CLI (bb_live_7Fh2…), live
Scopes    messages:send, messages:read, contacts:read, contacts:write, webhooks:manage, account:read
Profile   default

Global flags

Every command takes these.

FlagWhat it does
--jsonPrint BlueBot’s answer as JSON, exactly as the API returns it (snake_case), with no decoration.
--api-key <key>Use this key for one command instead of the stored one.
--profile <name>Use this profile’s key.
--base-url <url>Talk to another BlueBot, such as one running locally (http://localhost:3100/api/v1).
--no-colorNo colour, as NO_COLOR does.
-y, --yesDon’t ask before deleting or replacing something. Without a terminal, commands that would ask refuse unless this is set.
-h, --helpHelp for the command.
-v, --versionThe CLI’s version.

Send a text

Terminal
bluebot send +15555550123 "Your table for four is ready."
Text
→ +1 555-555-0123  queued  msg_1Hq2LxW9cVbN3mKpRtYs0a

bluebot send <to> [text]. <to> is a phone number in any format (US numbers can leave off +1) or an email address the person uses for iMessage. Leave out the text, or write -, to read it from standard input: fortune | bluebot send +15555550123 -.

FlagWhat it does
--file <path>Attach a file, up to 25 MB. Repeat for up to 10. Each is uploaded first.
--at <time>Schedule it. An ISO 8601 time (2026-11-20T09:00, read in your account’s time zone when it has no zone) or a time from now (+10m, +2h, +1d).
--expires <time>Give up if it’s still waiting then. Same formats. Default: 24 hours after it’s due.
--service <auto|imessage|sms>Default auto.
--metadata <key=value>Add a metadata key. Repeat for more, up to 20.
--chat <chat_id>Text an existing group chat instead of one person; leave out <to>.
--template <tpl_id>Send one of your templates instead of words; leave out the text. bluebot templates lists them.
--var <key=value>A value for one of the template’s variables, such as --var showing="412 Oak St". Repeat for more. A variable you leave out comes from the contact or your business name, else from the template’s own fallback; one with none of those stops the send, naming it.
--idempotency-key <key>Your own idempotency key. By default the CLI makes one per command, so re-running a command sends again.
--waitWait until the text settles: sent, failed, expired, canceled or unconfirmed. A held text is waited for too, up to 10 minutes; if it’s still waiting then, the command prints its hold and exits 0, and the text goes when the hold ends.

Sent texts show in blue for iMessage and green for SMS once the service is known. A text that can’t go yet prints its hold:

Text
→ +1 555-555-0123  queued, held: quiet hours until 8:00 AM  msg_1Hq2LxW9cVbN3mKpRtYs0a

Texts from the CLI show source: "cli" in BlueBot.

Read your texts

Terminal
bluebot messages
bluebot messages --direction in --since 24h
bluebot messages --follow

bluebot messages (or bluebot messages list) prints your newest texts as a table: time, direction, the person, service, state and the first words.

FlagWhat it does
--state <state>Only texts in this state, such as failed.
--direction <in|out>Only texts in or out.
--person <number or email>Only texts with this person.
--chat <chat_id>Only texts in this group chat.
--source <source>Only texts from this source, such as api or mac.
--since <time>, --until <time>An ISO 8601 time, or a time ago (30m, 24h, 7d).
--limit <n>How many, 1 to 100. Default 20.
--allEvery matching text, following the cursor page after page.
--followKeep running and print each text as it comes in or goes out. Press Ctrl-C to stop.

--follow reads your account’s event feed every two seconds, so it shows texts typed by hand on your Mac or iPhone too, as BlueBot copies them.

The other messages commands:

CommandWhat it does
bluebot messages get <id>One text, in full: its times, hold, failure and files.
bluebot messages edit <id> [--text <words>] [--at <time>] [--expires <time>]Change a scheduled text before it’s due.
bluebot messages cancel <id>Cancel a scheduled or queued text.
bluebot messages resolve <id> --mark-sentSettle an unconfirmed text that you checked went.
bluebot messages resolve <id> --send-againSettle an unconfirmed text by sending it again as a new text.

Check your Mac

Terminal
bluebot status
Text
Mac       online, MacBook Air (BlueBot 1.4.2, macOS 15.1), last check-in 6 seconds ago
Checks    Messages ok, Full Disk Access ok, Text Message Forwarding ok, Awake ok, FileVault ok
Today     41 sent, 6 of 50 new people, daily limit 1,000, resets at 12:00 AM
Queue     3 queued, 1 held, 0 unconfirmed
BlueBot   sending normally

status reads GET /v1/device and GET /v1/status. A check that needs you is shown in yellow with what the Mac saw and what to do. It exits 0 either way; add --json and read checks_ok to use it in a script.

Look up a number

Terminal
bluebot lookup +15555550123
Text
+1 555-555-0123  imessage (from your history, last text 2 days ago)

It reads your own Messages history through your Mac and waits up to 15 seconds. A number you’ve never texted is unknown, because Apple gives no way to ask.

Files, templates, conversations, chats, contacts, stops, settings and events

CommandWhat it does
bluebot files upload <path> [--name <name>]Upload a file and print its file_ id, to send later with the API.
bluebot files get <id> [--download <path>]A file’s details; --download saves its bytes.
bluebot templates [--search <words>]Your templates, with the variables each one takes.
bluebot templates get <id>One template, with its words.
bluebot conversations [--search <words>] [--filter <all|unread|waiting|archived>]Your conversations, newest first, as the inbox shows them. waiting means the last text is theirs. all, the default, leaves out archived ones.
bluebot conversations get <id>One conversation and its newest text.
bluebot chats [--groups] [--person <number>]Your chats, newest first; --groups for group chats only.
bluebot contacts [--search <words>] [--tag <tag>] [--stopped]The people you text; --search matches a name, company, number or email.
bluebot contacts get <id>One contact.
bluebot contacts add <number or email> [--first-name] [--last-name] [--company] [--tag <tag>] [--field <key=value>]Add somebody with their details ahead of time. Repeat --tag and --field for more. A number that’s already a contact is refused; change it with update.
bluebot contacts update <id> [--first-name] [--last-name] [--company] [--tags <a,b>] [--field <key=value>]Change a contact. --tags replaces all its tags.
bluebot contacts delete <id>Delete a contact. Asks first.
bluebot stopsYour stop list.
bluebot stops add <number or email>Stop texting somebody. Their queued texts are canceled.
bluebot stops remove <number or email>Take somebody off the stop list.
bluebot settingsYour sending rules.
bluebot settings set <key>=<value> …Change them, such as pace_seconds=30 quiet_hours.start=20:00 time_zone=America/Chicago.
bluebot pause [--for <duration> | --until <time>]Pause all sending, by default for an hour. Texts wait and go when you resume.
bluebot resumeResume sending now.
bluebot events [--type <types>] [--since <time>]The event feed, newest first; --type takes comma-separated types.
bluebot events get <id>One event and its payload.

Webhooks

CommandWhat it does
bluebot webhooksYour webhooks, with their status and last answer.
bluebot webhooks create <url> --events <types> [--description <words>]Make one. Prints its signing secret once. --events '*' for every type.
bluebot webhooks get <id>One webhook.
bluebot webhooks update <id> [--url] [--events] [--description] [--pause | --resume]Change one. Resuming sends what waited from the last 72 hours.
bluebot webhooks delete <id>Delete one. Asks first.
bluebot webhooks test <id> [--type <type>]Have BlueBot send an example event to it, signed with its real secret. Default type message.received.
bluebot webhooks rotate-secret <id>A new secret, printed once. The old one keeps signing beside it for 24 hours. Asks first.
bluebot webhooks deliveries <id> [--state <state>]Its deliveries: the event, the tries, the last status and error.
bluebot webhooks replay <id> <delivery_id>Send one delivery again.
bluebot webhooks trigger <type> --forward-to <url>Post an example of an event type to a local URL, signed with your local secret. It needs no key and no network.
bluebot webhooks trigger <type> --webhook <id>The same as webhooks test <id> --type <type>.

Listen for events on your machine

A real webhook needs a public https address. While you build the handler, listen stands in for one: it reads your account’s event feed and posts each event to a URL on your machine, http://localhost included.

Terminal
bluebot listen --forward-to http://localhost:3000/bluebot/webhooks
Text
Ready. Forwarding events to http://localhost:3000/bluebot/webhooks
Your local signing secret is whsec_c2VjcmV0LWZvci1sb2NhbC10ZXN0aW5nLW9ubHk=
Set it as BLUEBOT_WEBHOOK_SECRET in your handler. Press Ctrl-C to stop.

19:44:13  message.received   evt_7Kd3WnP9xQ2mLc5RvT8bHy  → 204 (38 ms)
19:44:31  message.sent       evt_2Mx8TqR4vB9nKd1WcY6pLs  → 204 (12 ms)
19:45:02  message.delivered  evt_9Pq4LmZ7wC3xNb6TkR1vYs  → 500 (210 ms)

How it works:

  • It starts from now, or from --since, and asks GET /v1/events for new events every two seconds, which uses 30 of your key’s 120 requests a minute.
  • It posts each event to --forward-to, oldest first, with the same body and the same three headers a real delivery has (webhook-id, webhook-timestamp, webhook-signature), signed with a secret that belongs to your machine. The CLI makes that secret the first time and keeps it with your key, so it stays the same between runs; set it as BLUEBOT_WEBHOOK_SECRET, and the same verification code works locally and in production.
  • It prints each event and what your handler answered. It doesn’t retry: fix the handler and use --since to run the events through again.
  • It reads events only. It doesn’t make a webhook in your account, and your real webhooks keep receiving events as usual.
FlagWhat it does
--forward-to <url>Where to post. Required unless --print-json is set.
--events <types>Only these types, comma-separated, such as message.received,message.failed.
--since <time>Start from an earlier time (10m, 2h, or ISO 8601) and forward what happened since, then carry on. Events are kept 30 days.
--interval <seconds>How often to read the feed, 1 to 30. Default 2.
--print-jsonPrint each event as one line of JSON as well, or instead when there’s no --forward-to.
--print-secretPrint the local secret and exit: export BLUEBOT_WEBHOOK_SECRET=$(bluebot listen --print-secret).

Test events (from webhooks test) aren’t in the event feed, so listen never sees them. To try a handler on an event that hasn’t happened, run listen in one terminal and, in another, bluebot webhooks trigger message.failed --forward-to http://localhost:3000/bluebot/webhooks. Testing webhooks covers this and tunnels.

Open the docs or the dashboard

bluebot open docs opens these docs in your browser, and bluebot open dashboard opens the dashboard.

Shell completion

bluebot completion <bash|zsh|fish> prints a completion script. Add it to your shell, for example bluebot completion zsh > "${fpath[1]}/_bluebot" for zsh.

Output and scripting

In a terminal, commands print tables and short lines. With --json, they print the API’s JSON and nothing else, so you can pipe it to jq:

Terminal
bluebot messages --state failed --all --json | jq -r '.[] | [.to, .failure.reason] | @tsv'
  • A single record prints as the object the API returns.
  • A list prints the page as the API returns it (object, data, has_more, next_cursor); with --all, an array of every item.
  • listen --print-json and messages --follow --json print one JSON object per line.
  • Errors go to standard error. With --json, that’s BlueBot’s error body, { "error": { … } }.

Without --json, an error prints BlueBot’s message, hint, documentation link and request id:

Text
Error: Send `to` or `chat_id`. (invalid_request)
Hint: Check the fields named in the message and send it again.
Docs: https://usebluebot.com/docs/errors/invalid_request
Request: req_1Hq2LxW9cVbN3mKpRtYs0a

Colour, BlueBot’s face and other decoration never appear in errors or in --json output.

Exit codes

CodeMeaning
0It worked. listen and --follow exit 0 when you press Ctrl-C.
1BlueBot refused the request (any error code but those below), or something unexpected happened.
2The command was wrong: an unknown command or flag, or a missing argument.
3Not signed in, or the key was refused: unauthorized or insufficient_scope.
4BlueBot couldn’t be reached, after retries.
5send --wait: the text failed, expired, was canceled or is unconfirmed.

The CLI retries what is safe to retry, as the SDKs do: connection failures, 429 and 5xx answers, twice, waiting what BlueBot asks.

Settings and environment

VariableWhat it does
BLUEBOT_API_KEYUse this key instead of the stored one. --api-key beats it.
BLUEBOT_PROFILEThe profile to use when --profile isn’t given.
BLUEBOT_BASE_URLThe API to talk to. Default https://usebluebot.com/api/v1.
BLUEBOT_CONFIG_DIRWhere the CLI keeps its settings.
NO_COLORAny value turns colour off.
BLUEBOT_LOGdebug prints each request’s method, path, status, time and request id to standard error. Bodies are never printed.

Settings live in config.json in the config folder: $BLUEBOT_CONFIG_DIR, else $XDG_CONFIG_HOME/bluebot, else ~/.config/bluebot on macOS and Linux and %APPDATA%\bluebot on Windows. It holds each profile’s account, the start of its key and its base URL; keys and local signing secrets live in the keychain, or in credentials.json (mode 0600) where there is none.