mirror of
https://github.com/warmbly/warmbly.git
synced 2026-09-06 08:01:24 +00:00
Merge remote-tracking branch 'origin/main' into feature/mailbox-fair-use-allowance
This commit is contained in:
@@ -17,6 +17,28 @@ The `wmbly_` prefix identifies the key as a Warmbly key. The remaining 43 charac
|
||||
|
||||
Keys are stored as a SHA-256 hash; the plaintext is shown exactly once on creation. To help you spot a key in the dashboard without exposing the secret, we store the first 8 characters (`key_prefix`, e.g. `wmbly_ab`) and the last 4 characters (`key_suffix`, e.g. `wxyz`). Render them as `wmbly_ab…wxyz`.
|
||||
|
||||
## Three ways to get a key
|
||||
|
||||
1. **The dashboard.** Settings > API keys, pick the scopes, copy the secret. This is the right path for a key a server will use.
|
||||
2. **The CLI.** [`warmbly auth login`](/api/cli/) opens a browser approval and mints a key named for the machine that asked, then stores it at 0600. This is the right path for a key you will use yourself, and it is the only one that does not involve pasting a secret into a shell.
|
||||
3. **The API.** `POST /v1/api-keys` with a key that carries `API_KEYS`. The secret is in that response and nowhere else.
|
||||
|
||||
All three produce the same thing: a `wmbly_` key with a scope bitmask, listed under Settings > API keys, revocable there.
|
||||
|
||||
### The CLI device flow
|
||||
|
||||
`warmbly auth login` uses a device-code handshake, so the terminal never handles your password and the browser never handles the key:
|
||||
|
||||
1. The CLI calls `POST /v1/auth/cli/code` with the scopes it wants and the machine's hostname. It gets back a `device_code` it keeps, a `user_code` it prints, and a `verification_uri_complete` it opens.
|
||||
2. You approve at `app.warmbly.com/cli`, choosing which workspace the key belongs to. The approval is what mints the key, so it requires the `MANAGE_API_KEYS` organization permission.
|
||||
3. The CLI polls `POST /v1/auth/cli/poll` and receives the key exactly once, on the first poll after approval.
|
||||
|
||||
Codes expire after ten minutes, both halves are per-IP rate limited, and the `device_code` is stored hashed. Anything else can drive the same flow: it is two public endpoints and a browser.
|
||||
|
||||
### Ending a key
|
||||
|
||||
`DELETE /v1/api-keys/:id` revokes any key in the workspace and needs the `API_KEYS` scope. `DELETE /v1/api-keys/self` revokes the key the call was made with and needs no scope at all, so a narrowly scoped credential can always end itself. This is what `warmbly auth logout` uses.
|
||||
|
||||
## Using your API key
|
||||
|
||||
Include your API key in the `Authorization` header of every request:
|
||||
|
||||
@@ -0,0 +1,382 @@
|
||||
---
|
||||
title: CLI
|
||||
description: The warmbly command line interface. Sign in once, then drive campaigns, contacts, mailboxes and the inbox from your terminal, from CI, or from an agent.
|
||||
---
|
||||
|
||||
`warmbly` is the command line interface to Warmbly. It signs in as you, holds one credential per host, and speaks only the public REST API, so it works against the hosted service and against any self-hosted instance you can reach.
|
||||
|
||||
```bash
|
||||
warmbly auth login
|
||||
warmbly campaign list
|
||||
warmbly inbox list --unseen
|
||||
warmbly api "/campaigns?limit=10"
|
||||
```
|
||||
|
||||
<Callout type="info" title="This is not warmblyctl">
|
||||
There are two CLIs and they answer different questions. `warmbly` is the one you install on your machine to use the product. [`warmblyctl`](/development/warmblyctl/) is the operator's tool: it talks to Postgres directly, runs inside the backend container, and exists for recovery, accounts, health and backups. If you are asking "what is wrong with this install", that is the one you want.
|
||||
</Callout>
|
||||
|
||||
## Install
|
||||
|
||||
Pick one. All of them produce the same single binary.
|
||||
|
||||
**macOS and Linux**
|
||||
|
||||
```bash
|
||||
curl -fsSL https://warmbly.com/cli.sh | sh
|
||||
```
|
||||
|
||||
Installs to `~/.local/bin`, so it needs no root and no toolchain. The script verifies what it downloaded against the published checksum and installs nothing if they disagree, writes shell completions, and tells you the one line to add to your profile if that directory is not already on your PATH.
|
||||
|
||||
**Windows**
|
||||
|
||||
```powershell
|
||||
irm https://warmbly.com/cli.ps1 | iex
|
||||
```
|
||||
|
||||
Installs to `%LOCALAPPDATA%\Warmbly\bin` and adds it to your user PATH. No admin rights.
|
||||
|
||||
**Homebrew** (macOS and Linux)
|
||||
|
||||
```bash
|
||||
brew install warmbly/tap/warmbly
|
||||
```
|
||||
|
||||
**Scoop** (Windows)
|
||||
|
||||
```powershell
|
||||
scoop bucket add warmbly https://github.com/warmbly/homebrew-tap
|
||||
scoop install warmbly
|
||||
```
|
||||
|
||||
**Docker**, for CI or anywhere installing a binary is more trouble than pulling one:
|
||||
|
||||
```bash
|
||||
docker run --rm -e WARMBLY_TOKEN ghcr.io/warmbly/warmbly/cli campaign list
|
||||
```
|
||||
|
||||
A container has no browser, so sign in with `WARMBLY_TOKEN` rather than `auth login`.
|
||||
|
||||
**From source**, if you have Go:
|
||||
|
||||
```bash
|
||||
go install github.com/warmbly/warmbly/cmd/cli@latest
|
||||
mv "$(go env GOPATH)/bin/cli" "$(go env GOPATH)/bin/warmbly"
|
||||
```
|
||||
|
||||
The package directory is `cmd/cli`, so `go install` names the binary `cli`. Rename it, or use one of the channels above.
|
||||
|
||||
**Already have a Warmbly instance?** The binary ships inside the backend image:
|
||||
|
||||
```bash
|
||||
docker compose -p warmbly exec backend warmbly --help
|
||||
```
|
||||
|
||||
### Keeping it current
|
||||
|
||||
```bash
|
||||
warmbly version
|
||||
warmbly upgrade
|
||||
```
|
||||
|
||||
`upgrade` replaces the binary in place, verifying the download against the release checksums first. When the CLI came from Homebrew or Scoop it says which command to run instead, rather than overwriting a file a package manager owns. The CLI also checks for a new release once a day and mentions it in one line; `WARMBLY_NO_UPDATE_CHECK=1` turns that off, and it never runs in CI or when output is piped.
|
||||
|
||||
### Installer flags
|
||||
|
||||
The install script takes flags after `--`, and every one of them is also an environment variable, so the same install runs from Ansible, cloud-init or a Dockerfile:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://warmbly.com/cli.sh | sh -s -- --dir /usr/local/bin
|
||||
curl -fsSL https://warmbly.com/cli.sh | sh -s -- --version v1.4.0
|
||||
curl -fsSL https://warmbly.com/cli.sh | sh -s -- --no-modify-path --no-completions
|
||||
curl -fsSL https://warmbly.com/cli.sh | sh -s -- --dry-run
|
||||
curl -fsSL https://warmbly.com/cli.sh | sh -s -- --uninstall
|
||||
```
|
||||
|
||||
| Flag | Variable | What it does |
|
||||
|---|---|---|
|
||||
| `--dir PATH` | `WARMBLY_INSTALL_DIR` | Where the binary goes. Default `~/.local/bin` |
|
||||
| `--version TAG` | `WARMBLY_CLI_VERSION` | Pin a release instead of taking the newest |
|
||||
| `--base-url URL` | `WARMBLY_CLI_BASE_URL` | Download from an internal mirror of the release assets |
|
||||
| `--no-modify-path` | `WARMBLY_NO_MODIFY_PATH` | Never touch a shell profile |
|
||||
| `--no-completions` | `WARMBLY_NO_COMPLETIONS` | Skip the completion files |
|
||||
| `--dry-run` | | Print what would happen, change nothing |
|
||||
| `--uninstall` | | Remove the binary and completions, keep your sign-ins |
|
||||
|
||||
`--help` lists them in the terminal. `sh -s -- --help` works through the pipe.
|
||||
|
||||
### Reading it before you run it
|
||||
|
||||
Piping a script into a shell is worth being careful about, which is why the checksum is published next to it:
|
||||
|
||||
```bash
|
||||
curl -fsSLO https://warmbly.com/cli.sh
|
||||
curl -fsSLO https://warmbly.com/cli.sh.sha256
|
||||
sha256sum -c cli.sh.sha256
|
||||
less cli.sh && sh cli.sh
|
||||
```
|
||||
|
||||
Every release archive is covered by `checksums.txt` on the release, and the installer verifies the archive against it before unpacking. If the two disagree it installs nothing and says so.
|
||||
|
||||
### Completions
|
||||
|
||||
The installer writes them for your shell. To do it by hand:
|
||||
|
||||
```bash
|
||||
warmbly completion bash > /etc/bash_completion.d/warmbly
|
||||
warmbly completion zsh > "${fpath[1]}/_warmbly"
|
||||
warmbly completion fish > ~/.config/fish/completions/warmbly.fish
|
||||
```
|
||||
|
||||
## Signing in
|
||||
|
||||
```bash
|
||||
warmbly auth login
|
||||
```
|
||||
|
||||
It asks two questions: which instance (the hosted service, or a hostname of your own), and how (a browser approval, or pasting a key you already have).
|
||||
|
||||
The browser path shows an eight character code, opens `app.warmbly.com/cli`, and waits. You approve there, choosing which workspace the CLI is signing in to. The approval creates **one API key named for your machine**, which appears under Settings > API keys and is revocable there or with `warmbly auth logout`. The terminal never handles your password, and the browser never handles the key.
|
||||
|
||||
```
|
||||
Your code: K4TM-9RQD
|
||||
Approve at: https://app.warmbly.com/cli?code=K4TM-9RQD
|
||||
|
||||
… Waiting for approval (the code expires in 10 minutes)
|
||||
✓ Signed in to warmbly.com as jane@example.com
|
||||
Workspace Acme
|
||||
```
|
||||
|
||||
Non-interactive forms, for a script or an agent:
|
||||
|
||||
```bash
|
||||
warmbly auth login --hostname warmbly.acme.com --web
|
||||
echo "$WARMBLY_KEY" | warmbly auth login --with-token
|
||||
warmbly auth login --scopes read-only
|
||||
```
|
||||
|
||||
### Scopes
|
||||
|
||||
The CLI asks for full access by default, and the approval screen lists exactly what that means before you agree. Narrow it with `--scopes`:
|
||||
|
||||
```bash
|
||||
warmbly auth login --scopes read-only
|
||||
warmbly auth login --scopes read_campaigns,read_contacts,send_campaigns
|
||||
```
|
||||
|
||||
A key's scopes are fixed once created, so widening them means a new key. `warmbly auth refresh --scopes full` runs the sign-in again and revokes the key it replaces, which is why refreshing does not leave a trail of keys behind.
|
||||
|
||||
Scope names are the ones in the [permissions reference](/api/permissions/), in either case, with `full` and `read-only` as shorthands.
|
||||
|
||||
### Several instances
|
||||
|
||||
The CLI holds one credential per host. The `*` in `auth status` is the one commands use.
|
||||
|
||||
```bash
|
||||
warmbly auth login --hostname warmbly.acme.com
|
||||
warmbly auth status
|
||||
warmbly auth switch warmbly.acme.com
|
||||
warmbly campaign list --host warmbly.com # one command, other host
|
||||
```
|
||||
|
||||
`warmbly auth status` also tells you where the token came from, which is the answer nine times out of ten when a command fails unexpectedly:
|
||||
|
||||
```
|
||||
* warmbly.com
|
||||
✓ signed in as jane@example.com
|
||||
- workspace: Acme
|
||||
- scopes: all 24
|
||||
- api: https://api.warmbly.com
|
||||
- token from: /home/jane/.config/warmbly/hosts.yml
|
||||
- token: wmbly_ab********wxyz
|
||||
```
|
||||
|
||||
### In CI
|
||||
|
||||
Set `WARMBLY_TOKEN` and skip the login entirely. It overrides the file and is never written to it.
|
||||
|
||||
```yaml
|
||||
env:
|
||||
WARMBLY_TOKEN: ${{ secrets.WARMBLY_TOKEN }}
|
||||
WARMBLY_HOST: warmbly.com # omit for the hosted service
|
||||
run: warmbly campaign list --json
|
||||
```
|
||||
|
||||
`warmbly auth token` prints the active token and nothing else, for handing to another tool.
|
||||
|
||||
## Output
|
||||
|
||||
Commands print a table on a terminal and JSON everywhere else, so the same command is readable by a person and parseable by a pipe.
|
||||
|
||||
```bash
|
||||
warmbly campaign list # a table
|
||||
warmbly campaign list > campaigns.json # JSON, no flag needed
|
||||
warmbly campaign list --json | jq '.data[].name'
|
||||
warmbly campaign list --fields name,status
|
||||
warmbly campaign list --template '{{range .data}}{{.name}}{{"\n"}}{{end}}'
|
||||
```
|
||||
|
||||
`--all` walks the cursor on any list command and merges every page into one response.
|
||||
|
||||
## Sending real mail
|
||||
|
||||
Anything that puts mail on the wire asks first, and refuses rather than sending when there is no terminal to ask on:
|
||||
|
||||
```
|
||||
$ warmbly campaign start 6f1c…
|
||||
! `warmbly campaign start` sends real mail. Continue? [y/N]
|
||||
```
|
||||
|
||||
`--yes` is the only way past it, which makes it the flag to grep for in a script review. The commands that behave this way are `campaign start`, `campaign test`, `mailbox send`, `inbox reply`, `inbox compose` and `inbox approve-draft`.
|
||||
|
||||
## Commands
|
||||
|
||||
Run `warmbly <command> --help` for the flags, and `warmbly <command> <subcommand> --help` for one command's arguments.
|
||||
|
||||
| Command | What it covers |
|
||||
|---|---|
|
||||
| `auth` | login, logout, status, token, switch, refresh |
|
||||
| `status` | one screen: mailboxes needing attention, what is sending, what is unread |
|
||||
| `browse` | open the dashboard, or one record, in a browser |
|
||||
| `campaign` | list, view, create, edit, steps, senders, segments, preflight, test, start, stop, logs |
|
||||
| `contact` | list, view, create, edit, delete, lookup, timeline, notes, import, export, verify |
|
||||
| `mailbox` | list, view, edit, health checks, sync state, sending behaviour, warmup, hold, send |
|
||||
| `inbox` | list, view, threads, read, reply, compose, drafts, scheduled sends, snoozes |
|
||||
| `suppression` | the addresses and domains that get no campaign mail |
|
||||
| `segment` | live audiences and their conditions |
|
||||
| `template` | reply templates |
|
||||
| `automation` | automations, their runs and test firing |
|
||||
| `form` | lead capture forms, submissions and stats |
|
||||
| `deal`, `pipeline`, `task` | the CRM |
|
||||
| `analytics` | dashboard, deliverability, warmup, per-mailbox and per-campaign numbers |
|
||||
| `audit` | the workspace's audit trail |
|
||||
| `advisor` | recommendations, and applying or dismissing them |
|
||||
| `webhook` | endpoints, deliveries, redelivery, event types |
|
||||
| `key` | API keys, their scopes and their usage |
|
||||
| `oauth-app` | OAuth applications you publish |
|
||||
| `integration` | third-party connections |
|
||||
| `org` | which workspace this credential belongs to |
|
||||
| `team` | named groups of members, for CRM ownership and routing |
|
||||
| `settings` | workspace-wide outreach and suppression settings |
|
||||
| `warmup-routing` | which mailboxes warm with which |
|
||||
| `tool` | the AI tool registry, listed and called |
|
||||
| `events` | the live event stream |
|
||||
| `api` | any endpoint at all |
|
||||
| `upgrade` | replace this binary with the newest release |
|
||||
| `config`, `alias`, `completion`, `version` | the CLI itself |
|
||||
|
||||
### Examples
|
||||
|
||||
```bash
|
||||
# Create a campaign, add a step, check it, start it
|
||||
warmbly campaign create --name "Q3 outbound" --daily-limit 40
|
||||
warmbly campaign add-step CAMPAIGN_ID --subject "Quick question" --wait-after 0
|
||||
warmbly campaign preflight CAMPAIGN_ID
|
||||
warmbly campaign start CAMPAIGN_ID
|
||||
|
||||
# Mailbox health across the workspace
|
||||
warmbly mailbox list
|
||||
warmbly mailbox check MAILBOX_ID
|
||||
warmbly mailbox edit MAILBOX_ID --daily-limit 40
|
||||
|
||||
# The inbox
|
||||
warmbly inbox list --unseen --limit 20
|
||||
warmbly inbox thread --email-id EMAIL_ID
|
||||
|
||||
# Contacts in and out
|
||||
warmbly contact create --email jane@example.com --first-name Jane --company Acme
|
||||
warmbly contact list --all --json > contacts.json
|
||||
```
|
||||
|
||||
## What needs the dashboard
|
||||
|
||||
Three things the CLI deliberately does not do, because the API does not let a key do them:
|
||||
|
||||
- **Connecting a mailbox.** It needs OAuth consent or a credential form in a browser. `warmbly browse mailboxes` opens the right page.
|
||||
- **Workspace administration.** Members, roles, invitations, workspace exports and the danger zone are session-only on the API: they depend on a human-bound session and refuse an API key. `warmbly browse members` and `warmbly browse settings` open them.
|
||||
- **Billing.** Plans, checkout and credits are session-only for the same reason. `warmbly browse billing`.
|
||||
|
||||
`warmbly org view` still shows which workspace you are in, and `warmbly team`, `warmbly settings` and `warmbly audit` cover the workspace surface a key can reach.
|
||||
|
||||
## Watching events
|
||||
|
||||
`warmbly events tail` streams the developer WebSocket into your terminal: the same events the dashboard runs on, printed as they happen. It is the fastest way to see whether an integration is receiving what you think it is, without standing up a public endpoint first.
|
||||
|
||||
```bash
|
||||
warmbly events tail
|
||||
warmbly events tail --intent EMAIL --intent CAMPAIGN
|
||||
warmbly events tail --json | jq 'select(.event_type == "EMAIL_REPLIED")'
|
||||
```
|
||||
|
||||
It needs a key with `REALTIME_SUBSCRIBE`; `warmbly auth refresh --scopes full` gets one. The stream, its intents and its event types are documented under [Realtime](/api/realtime/).
|
||||
|
||||
## Calling the API directly
|
||||
|
||||
`warmbly api` reaches every endpoint, including the ones with no command of their own. Paths are relative to `/v1`.
|
||||
|
||||
```bash
|
||||
warmbly api /me
|
||||
warmbly api "/campaigns?limit=10" --paginate
|
||||
warmbly api /contacts -f email=jane@example.com -f first_name=Jane
|
||||
warmbly api /campaigns/CAMPAIGN_ID -X PATCH -F daily_limit=40
|
||||
warmbly api /contacts/search -X POST --input filter.json
|
||||
warmbly api /webhooks/WEBHOOK_ID -X DELETE
|
||||
```
|
||||
|
||||
`-f` keeps a value a string. `-F` guesses the type, so `true`, `false`, `null` and numbers arrive as themselves, `@file` reads a value from a file, `key[sub]=v` nests and repeated `key[]=v` builds an array. `--paginate` follows the cursor, `-i` includes the status and headers, `--idempotency-key` rides the [documented header](/api/authentication/).
|
||||
|
||||
## Configuration
|
||||
|
||||
Two files under `~/.config/warmbly` (or `XDG_CONFIG_HOME`, or `WARMBLY_CONFIG_DIR`):
|
||||
|
||||
- `hosts.yml`, one credential per host, written 0600
|
||||
- `config.yml`, preferences and aliases
|
||||
|
||||
Signing in records the instance's own API and dashboard URLs alongside the credential, taken from what the instance reports, so `browse` and `events tail` work on a self-hosted layout without anyone configuring a second address.
|
||||
|
||||
```bash
|
||||
warmbly config list
|
||||
warmbly config set output json # default to JSON even on a terminal
|
||||
warmbly config set confirm always # confirm every write, not only sends
|
||||
warmbly config set browser firefox
|
||||
```
|
||||
|
||||
### Aliases
|
||||
|
||||
```bash
|
||||
warmbly alias set hot "campaign list --status active"
|
||||
warmbly hot --json
|
||||
```
|
||||
|
||||
Anything you type after the alias is appended, so an alias is a starting point rather than a fixed command.
|
||||
|
||||
### Environment
|
||||
|
||||
| Variable | What it does |
|
||||
|---|---|
|
||||
| `WARMBLY_TOKEN` | The API key to use. Overrides `hosts.yml` and is never written to it |
|
||||
| `WARMBLY_API_KEY` | The same thing under the name `warmblyctl` uses |
|
||||
| `WARMBLY_HOST` | Which signed-in host to use |
|
||||
| `WARMBLY_API_URL` | The API base URL, when it is not derivable from the host |
|
||||
| `WARMBLY_CONFIG_DIR` | Where the two files live |
|
||||
| `WARMBLY_NO_UPDATE_CHECK` | Never check for a newer release |
|
||||
| `NO_COLOR` | Turns colour off, as everywhere else |
|
||||
|
||||
## Exit codes
|
||||
|
||||
| Code | Meaning |
|
||||
|---|---|
|
||||
| `0` | It worked |
|
||||
| `1` | The command failed, or you declined a prompt |
|
||||
| `2` | The command line was wrong, or an answer was needed with no terminal to ask on |
|
||||
| `4` | Not signed in, or the credential was rejected or lacks a scope |
|
||||
|
||||
Every API failure prints the response's machine-readable `code` and `request_id` to stderr, so a script can branch without reading prose.
|
||||
|
||||
## See also
|
||||
|
||||
- [Authentication](/api/authentication/) for how the device flow mints a key
|
||||
- [Permissions](/api/permissions/) for what each scope allows
|
||||
- [Endpoint scope map](/api/endpoints/) for what `warmbly api` can reach
|
||||
- [Realtime](/api/realtime/) for the stream behind `warmbly events tail`
|
||||
- [warmblyctl](/development/warmblyctl/) for the operator's CLI
|
||||
@@ -266,6 +266,9 @@ Changing Advisor settings (`PATCH /advisor/settings`) is JWT only, alongside the
|
||||
| GET | `/api-keys/:id` | `API_KEYS` |
|
||||
| PATCH | `/api-keys/:id` | `API_KEYS` |
|
||||
| DELETE | `/api-keys/:id` | `API_KEYS` |
|
||||
| DELETE | `/api-keys/self` | none |
|
||||
|
||||
`DELETE /api-keys/self` revokes the key the request was made with, and is the one route here that needs no scope. A credential must always be able to end itself: requiring `API_KEYS` to sign out would leave a read-only key on a laptop someone is handing back live, which is what [`warmbly auth logout`](/api/cli/) promises to prevent. A JWT caller gets a `400`: there is no key in that request to end, only a session, which `POST /auth/logout` ends.
|
||||
|
||||
### OAuth apps
|
||||
|
||||
@@ -339,6 +342,8 @@ These never accept an API key. They depend on a human-bound session: billing flo
|
||||
|
||||
`GET /auth/config` gained two fields: `invites_required` (boolean, true when an invitation token is needed to create an account) and `docs_url` (string, the deployment's link to the accounts and access documentation, for a client to surface next to a refusal).
|
||||
|
||||
`GET /auth/config` also carries `websocket_url` and `app_url` (both strings, each omitted when the instance has none). They are the realtime gateway a developer client connects to and the dashboard origin a client sends someone to. Both are served here because on a self-hosted instance the host layout is whatever the operator chose, and there is no other way to discover it: the [CLI](/api/cli/) reads them for `warmbly events tail` and `warmbly browse`.
|
||||
|
||||
`GET /auth/config` also carries `billing_enabled` (boolean). It is `false` when the deployment runs with `BILLING_PROVIDER=none`, which is the self-host default: every feature is unlocked server-side, so the dashboard shows the workspace as self-hosted instead of on a free trial and hides the billing and referral pages. `self_hosted` alone does not imply this, because a self-hosted install may still run Stripe.
|
||||
- `POST /auth/setup` (first-run claim: exchanges the one-time token printed at boot for the owner account. Refused once any account exists)
|
||||
- `GET /auth/providers`, `POST /auth/apple`, `POST /auth/google` (native-app social sign-in)
|
||||
@@ -359,6 +364,7 @@ These never accept an API key. They depend on a human-bound session: billing flo
|
||||
- All of `/organization/*` (create, switch, members, invitations, transfer ownership, avatar, danger zone)
|
||||
- `GET /website-tracking/settings`, `PATCH /website-tracking/settings`, `POST /website-tracking/settings/rotate-key` (the [website tracking](/guides/website-tracking/) snippet's consent mode, location precision, allowed hosts and retention; JWT permission `MANAGE_SETTINGS`. The rotate is bodyless and safe to repeat, each call issues a new key)
|
||||
- All of `/subscription/*` (checkout, portal, cancel, change-plan, preview-change, enterprise-inquiry, discounts, referrals, etc.)
|
||||
- All of `/auth/cli/*` except the two handshake routes below (`GET /auth/cli/codes/:code`, `POST /auth/cli/codes/:code/approve`, `POST /auth/cli/codes/:code/deny`: the browser half of `warmbly auth login`, where a signed-in member reviews the code a CLI is showing and authorizes it. Approving mints an ordinary API key, so it requires the `MANAGE_API_KEYS` organization permission and is session-only: an API key must not be able to mint another one this way)
|
||||
- All of `/pool-link/*` and `/cloud-link/*` (the self-hosted warmup pool link: approving an instance's code, listing and unlinking instances, and on a self-hosted instance the connect flow and mailbox enrollment). `POST /pool-link/codes` and `POST /pool-link/poll` are public and per-IP rate limited: they are the device-code handshake an instance uses before it has a token, and `/pool-link/instance/*` accepts only an instance token. `/pool-link/instance/oauth/*`, `/pool-link/instance/mailboxes/:id/token`, `/pool-link/instance/workspace-mailboxes` and `/pool-link/instance/mailboxes/adopt` are the cloud-managed mailbox surface (Google and Microsoft sign-in on Warmbly's OAuth apps, brokered access tokens); their instance-side counterparts are `/cloud-link/oauth/*` and `/cloud-link/workspace-mailboxes/*`
|
||||
- All of `/admin/*`
|
||||
|
||||
@@ -437,6 +443,7 @@ External MCP servers whose tools the assistant can use (see [Connect MCP tools](
|
||||
## Public
|
||||
|
||||
- `GET /health`
|
||||
- `POST /auth/cli/code`, `POST /auth/cli/poll` (the [CLI](/api/cli/) device-code handshake, per-IP rate limited. Public by necessity: the CLI has no credential until the flow completes. `POST /auth/cli/code` returns `device_code`, `user_code`, `verification_uri`, `verification_uri_complete`, `expires_in` and `interval`; polling returns `{"status":"pending"}` until a member decides, then `{"status":"approved"}` carrying the minted key exactly once, or `{"status":"denied"}`. An unknown or expired `device_code` is a `404`, so a poller cannot probe for live handshakes)
|
||||
- `POST /webhooks/github/releases` (HMAC-SHA256 signature)
|
||||
- `POST /webhook/stripe` (Stripe signature)
|
||||
- `POST /webhook/campaign`, `/webhook/email`, `/webhook/user-email` (Google OIDC token from Cloud Tasks)
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"index",
|
||||
"authentication",
|
||||
"sdks",
|
||||
"cli",
|
||||
"oauth",
|
||||
"permissions",
|
||||
"endpoints",
|
||||
|
||||
@@ -141,6 +141,7 @@ TRUSTED_PROXIES=10.0.0.0/8,172.16.0.0/12
|
||||
| `DISABLE_PASSWORD_LOGIN` | Turns off email and password entirely, for single sign-on only deployments | `false` | yes |
|
||||
| `SSO_AUTO_PROVISION` | `true` lets a verified identity provider assertion create an account regardless of `DISABLE_REGISTRATION` | `false` | yes |
|
||||
| `AUTH_IP_RATE_LIMIT` | Unauthenticated auth requests allowed per source IP per 15 minutes | `60` | yes |
|
||||
| `CLI_AUTH_IP_RATE_LIMIT` | CLI sign-in handshake requests allowed per source IP per 15 minutes. Its own budget, because one `warmbly auth login` polls around 200 times and must not exhaust the allowance above | `500` | yes |
|
||||
| `WARMBLY_BOOTSTRAP_EMAIL` | First owner's address, read only while the users table is empty | unset | yes |
|
||||
| `WARMBLY_BOOTSTRAP_PASSWORD_HASH` | Argon2 PHC string for that owner. Preferred over the plaintext form | unset | yes |
|
||||
| `WARMBLY_BOOTSTRAP_PASSWORD` | Plaintext convenience form. Warns at boot, and leaves a password in your process environment | unset | yes |
|
||||
|
||||
@@ -3,6 +3,12 @@ title: warmblyctl
|
||||
description: The CLI for a Warmbly instance. The operator commands for accounts, health and recovery, and the API commands that let scripts and AI agents drive campaigns, contacts, mailboxes and the inbox with an API key.
|
||||
---
|
||||
|
||||
<Callout type="info" title="There are two CLIs. This is the operator's one.">
|
||||
`warmblyctl` is for running an instance: accounts, health, recovery, backups. It reads the database directly and is meant to be run inside the backend container.
|
||||
|
||||
If you want to use the product from your terminal (campaigns, contacts, mailboxes, the inbox), you want [`warmbly`](/api/cli/): a signed-in, multi-host client you install on your own machine, with `warmbly auth login` instead of an exported key.
|
||||
</Callout>
|
||||
|
||||
`warmblyctl` is the CLI for a Warmbly instance, and it has two halves with two trust models.
|
||||
|
||||
The operator commands answer two questions: what state is this install in, and how do I get back in. They read and write the database directly, so they keep working when the sign-in page does not. Their authorization is container or host access, the same trust model as Sentry's `createuser`, Gitea's `admin user create` and authentik's `ak changepassword`, and it is the right one when the identity system is the thing that is broken.
|
||||
@@ -11,6 +17,8 @@ The [API commands](#the-api-commands) drive a running instance over its public R
|
||||
|
||||
The CLI never serves HTTP. The API commands are a client of the already-gated public API, which adds no new surface to your instance.
|
||||
|
||||
Those API commands predate the [`warmbly` CLI](/api/cli/) and still work exactly as documented below. For day-to-day product work prefer `warmbly`: it signs in for you, holds a credential per host, prints tables rather than raw JSON, and runs on your laptop rather than inside a container.
|
||||
|
||||
## Running it
|
||||
|
||||
The binary ships inside the backend image at `/usr/local/bin/warmblyctl`, so it is on the path in every runtime. Running it inside the backend is the documented path because the environment there is already correct.
|
||||
@@ -462,7 +470,7 @@ Paths are relative to `/v1`. `--data` takes a JSON literal, `-` for stdin, or `@
|
||||
|
||||
### For AI agents
|
||||
|
||||
The repository ships skills under `skills/` (`warmbly-api` for the product, `warmbly-ops` for instance administration, `warmbly-install` for standing an instance up and moving it) that teach a coding agent these commands, the conventions above, and the sending-safety rules. Install them the way your agent expects, for example `cp -r skills/warmbly-api ~/.claude/skills/` for Claude Code, or point the agent at the `SKILL.md` directly. An agent given a scoped key and those skills can operate a workspace end to end without touching the database.
|
||||
The repository ships skills under `skills/` (`warmbly-cli` for the [`warmbly` CLI](/api/cli/), `warmbly-api` for the same product surface through `warmblyctl`, `warmbly-ops` for instance administration, `warmbly-install` for standing an instance up and moving it) that teach a coding agent these commands, the conventions above, and the sending-safety rules. Install them the way your agent expects, for example `cp -r skills/warmbly-api ~/.claude/skills/` for Claude Code, or point the agent at the `SKILL.md` directly. An agent given a scoped key and those skills can operate a workspace end to end without touching the database.
|
||||
|
||||
## When Redis is down
|
||||
|
||||
|
||||
Reference in New Issue
Block a user