The zeebra CLI
Use your workspace from the command line, or let an AI agent such as Claude Code or Codex use it, with one small script.
zeebra is a small command-line tool for the Zeebra API. Every API operation is a command, with the same checks and limits as the app. It suits scripts and AI coding agents (Claude Code, Codex, Cursor): they can run zeebra ops to see what's possible and zeebra <operation> --help to see how.
Install
It is one PHP file and needs PHP 8.2 or newer (macOS with Homebrew PHP, Linux, or Windows with WSL).
curl -fsSL https://zeebra.ai/cli/zeebra -o zeebra
chmod +x zeebra
sudo mv zeebra /usr/local/bin/ # or anywhere on your PATH
Log in
Create a full access API key in Settings → API, then:
zeebra login --url https://zeebra.ai
It asks for the key, checks it, and saves both in ~/.config/zeebra/config.json, readable only by you. zeebra logout removes them. In CI or for an agent you can set ZEEBRA_URL and ZEEBRA_API_KEY instead; they win over the saved login.
Run operations
zeebra ops # every operation, by section
zeebra ops campaign # only the ones that mention "campaign"
zeebra list_contacts --help # an operation's inputs
zeebra list_contacts --filter status=active --filter tag=VIP --per-page 50
zeebra get_contact --id 1234
zeebra create_campaign --name "November news" --format markdown --markdown @news.md --subject "What's new"
zeebra add_to_list --id 3 --contact-ids 12,13,14
zeebra create_webhook --json '{"url": "https://example.com/hooks/zeebra", "events": ["contact.unsubscribed"]}'
- Inputs are
--name value(or--name=value); dashes and underscores both work (--per-page,--per_page). A bare--flagistrue. @filereads a value from a file (--markdown @email.md,--csv @contacts.csv);@-reads standard input.- Lists take commas, repeats or JSON:
--tags a,b,--tags a --tags b,--tags '["a","b"]'. - Objects take
key=value(repeat it) or JSON:--filter status=active,--where '{"all": […]}'. --json '{…}'(or--json @input.json) gives all inputs at once;--optionsnext to it win.- Operation names work with dashes too:
zeebra list-contacts.
Output
In a terminal, results are a table; when piped (or for an agent) they are the API's JSON ({"data": …, "meta": …}). Choose with -o json or -o table. Lists in pages say how to get the next one (--page 2).
Destructive operations
Operations that can't be undone (deleting, sending a campaign) ask first in a terminal. Without a terminal they need --yes. An agent should show you what it is about to do and only then add --yes. Sending a campaign also needs --confirm-recipients with the count from review_campaign, as in the API.
Exit codes
| Code | Meaning |
|---|---|
| 0 | It worked. |
| 1 | Another error, or you answered "no". |
| 2 | The command was mistyped (an unknown input, a missing one, a wrong type). |
| 3 | Not logged in, or the key was refused or can't do this (401, 403). |
| 4 | Not found (404). |
| 5 | Invalid input or a conflict, like a campaign that can't be sent in its state (422, 409). |
| 6 | Too many requests: up to 300 a minute per key (429). |
| 7 | Zeebra or the network had a problem (5xx, no connection). |
Errors are printed to standard error as Error (code): message, with the inputs at fault.
Staying up to date
The list of operations comes from your Zeebra (https://zeebra.ai/cli/manifest.json) and is kept for an hour, so new operations show up without updating the CLI. zeebra ops --refresh fetches it now. If the CLI itself is too old for the server it says so; download it again with the same curl command.
For AI agents
Tell your agent to use zeebra, for example in CLAUDE.md or AGENTS.md:
Use the `zeebra` CLI for our email platform. Run `zeebra ops` to find an operation and
`zeebra <operation> --help` for its inputs. Output is JSON. Never add --yes or send a
campaign without asking me first.
Agents that speak MCP can also connect to the MCP server instead.