# Cooper CLI

> One Node file calls the Cooper Email REST API from a shell. Install it with curl or npx from https://cooperemail.com.

HTML: https://cooperemail.com/docs/cli

## Install

The Cooper CLI is one Node.js file (Node 18 or newer, no npm dependencies). Download it from https://cooperemail.com/cli/cooper.mjs, or run the npm tarball at https://cooperemail.com/cli/cooper-cli.tgz. Both URLs are on the apex host. www.cooperemail.com redirects those GETs to the apex.

The script calls the existing REST API. It does not add endpoints.

```bash
curl -fsSL https://cooperemail.com/cli/cooper.mjs -o cooper.mjs && node cooper.mjs help

npx -y https://cooperemail.com/cli/cooper-cli.tgz help
```

## Auth

The key is read in this order: --api-key, the COOPER_API_KEY environment variable, then the credentials file. The base URL is --base-url, then COOPER_BASE_URL, then https://cooperemail.com. http is accepted only for localhost and 127.0.0.1.

onboard --save writes {"api_key","base_url"} to ${XDG_CONFIG_HOME:-~/.config}/cooper/credentials.json with mode 0600. The saved base_url is a record of where the key was minted. Later commands still use --base-url, COOPER_BASE_URL, or the default. The full key is printed only in the onboard response. Other commands do not print it. Requests send Authorization: Bearer and User-Agent: cooper-cli/1.0.0.

## Quickstart in 4 commands

Save a key, list the inbox, send to that same address, then wait for the message to land.

```bash
node cooper.mjs onboard --username research-bot --save
node cooper.mjs inboxes list
node cooper.mjs send inb_example --to research-bot@cooperemail.com --subject "Hello" --text "Sent from the CLI."
node cooper.mjs wait inb_example --timeout 25
```

Sample output:

```json
{
  "account_id": "acc_example",
  "api_key": "coop_live_example",
  "api_key_id": "key_example",
  "inbox": {
    "id": "inb_example",
    "username": "research-bot",
    "email": "research-bot@cooperemail.com",
    "display_name": "Research Bot",
    "created_at": "2026-10-18T15:00:00.000Z",
    "require_sender_auth": true,
    "labels": [],
    "expires_at": null,
    "status": "active",
    "paused_at": null,
    "metadata": {},
    "client_id": null,
    "screening": "observe"
  }
}
```

## onboard

POST /api/v1/onboard. --username is required. --display-name, --key-name, and --owner-email match the JSON body. --save stores the returned api_key.

```bash
node cooper.mjs onboard --username research-bot --display-name "Research Bot" --owner-email human@example.com --save
```

Sample output:

```json
{
  "account_id": "acc_example",
  "api_key": "coop_live_example",
  "api_key_id": "key_example",
  "inbox": {
    "id": "inb_example",
    "username": "research-bot",
    "email": "research-bot@cooperemail.com",
    "display_name": "Research Bot",
    "created_at": "2026-10-18T15:00:00.000Z",
    "require_sender_auth": true,
    "labels": [],
    "expires_at": null,
    "status": "active",
    "paused_at": null,
    "metadata": {},
    "client_id": null,
    "screening": "observe"
  }
}
```

## me

GET /api/v1/me. The live body also includes workspace, lifecycle_emails, notices, whats_new, and owner_email_style. The CLI prints that body unchanged.

```bash
node cooper.mjs me
```

Sample output:

```json
{
  "account_id": "acc_example",
  "parent_account_id": null,
  "auth_type": "api_key",
  "key": {
    "id": "key_example",
    "name": "onboard",
    "prefix": "coop_live_examp",
    "inbox_id": null,
    "permissions": null,
    "expires_at": null
  }
}
```

## inboxes list, create, get

GET /api/v1/inboxes, POST /api/v1/inboxes, and GET /api/v1/inboxes/{id}. List filters are --label, --status, --q, --limit, and --page-token. Create accepts --username, --display-name, --domain, --ttl-hours, --expires-at, --status, --metadata, --client-id, and --notify-url.

```bash
node cooper.mjs inboxes list --limit 20
node cooper.mjs inboxes create --username research-bot
node cooper.mjs inboxes get inb_example
```

Sample output:

```json
{
  "object": "list",
  "data": [
    {
      "id": "inb_example",
      "username": "research-bot",
      "email": "research-bot@cooperemail.com",
      "display_name": "Research Bot",
      "created_at": "2026-10-18T15:00:00.000Z",
      "require_sender_auth": true,
      "labels": [],
      "expires_at": null,
      "status": "active",
      "paused_at": null,
      "metadata": {},
      "client_id": null,
      "screening": "observe"
    }
  ],
  "next_page_token": null
}
```

## messages list and get

GET /api/v1/inboxes/{id}/messages and GET /api/v1/inboxes/{id}/messages/{message_id}. --unread, --labels, --limit, --before, --after, --from, --to, --subject, --direction, --thread-id, --ascending, --page-token, and --channel are query parameters when you set them.

```bash
node cooper.mjs messages list inb_example --unread --limit 20
node cooper.mjs messages get inb_example msg_example
```

Sample output:

```json
{
  "object": "list",
  "inbox_id": "inb_example",
  "data": [
    {
      "id": "msg_example",
      "inbox_id": "inb_example",
      "subject": "Hello",
      "preview": "Sent from the CLI.",
      "labels": [
        "unread"
      ]
    }
  ],
  "next_page_token": null
}
```

## send

POST /api/v1/inboxes/{id}/messages. --to, --subject, and --text or --html are required. --cc, --bcc, --reply-to, and --client-id go in the JSON body. --idempotency-key is sent as the Idempotency-Key header.

```bash
node cooper.mjs send inb_example --to ada@example.com --subject "Hello" --text "Tuesday works." --idempotency-key send-1
```

Sample output:

```json
{
  "id": "msg_example",
  "inbox_id": "inb_example",
  "direction": "outbound",
  "subject": "Hello",
  "text": "Tuesday works.",
  "to": [
    "ada@example.com"
  ]
}
```

## reply

POST /api/v1/inboxes/{id}/messages/{message_id}/reply. --text or --html is required. --reply-all, --cc, --bcc, --reply-to, and --client-id go in the body. --idempotency-key is the Idempotency-Key header.

```bash
node cooper.mjs reply inb_example msg_example --text "Sounds good."
```

Sample output:

```json
{
  "id": "msg_reply",
  "inbox_id": "inb_example",
  "direction": "outbound",
  "in_reply_to": "msg_example",
  "text": "Sounds good."
}
```

## wait and code

wait calls GET /api/v1/inboxes/{id}/wait. code calls GET /api/v1/inboxes/{id}/wait-for-code. --timeout (or --wait) sets the wait query, in seconds. code also accepts --after, --from, and --subject.

```bash
node cooper.mjs wait inb_example --timeout 25
node cooper.mjs code inb_example --timeout 25 --from signup@example.com
```

Sample output:

```json
{
  "object": "inbox_wait",
  "inbox_id": "inb_example",
  "timed_out": false,
  "data": [
    {
      "id": "msg_example",
      "subject": "Hello"
    }
  ]
}
```

## threads list

GET /api/v1/inboxes/{id}/threads. --limit, --before, --after, --ascending, and --page-token are query parameters.

```bash
node cooper.mjs threads list inb_example --limit 20
```

Sample output:

```json
{
  "object": "list",
  "inbox_id": "inb_example",
  "data": [
    {
      "object": "thread",
      "id": "thr_example",
      "inbox_id": "inb_example",
      "subject": "Hello",
      "message_count": 1
    }
  ],
  "next_page_token": null
}
```

## drafts create, list, send

POST and GET /api/v1/inboxes/{id}/drafts, and POST /api/v1/inboxes/{id}/drafts/{draft_id}/send. --idempotency-key maps to the Idempotency-Key header on create and send.

```bash
node cooper.mjs drafts create inb_example --to ada@example.com --subject Quote --text "Ready for review."
node cooper.mjs drafts list inb_example
node cooper.mjs drafts send inb_example drf_example
```

Sample output:

```json
{
  "object": "draft",
  "id": "drf_example",
  "inbox_id": "inb_example",
  "subject": "Quote",
  "text": "Ready for review.",
  "send_status": "draft"
}
```

## api

api <METHOD> <path> calls any /api/v1 path with the same auth and User-Agent. --data is a JSON value, @file, or - for stdin. Paths outside /api/v1 are rejected before a request is sent.

```bash
node cooper.mjs api GET /api/v1/me
node cooper.mjs api POST /api/v1/inboxes/inb_example/messages --data '{"to":["ada@example.com"],"subject":"Hello","text":"Hi"}'
```

Sample output:

```json
{
  "account_id": "acc_example",
  "auth_type": "api_key"
}
```

## Exit codes

0 means the API returned HTTP 2xx. The body is on stdout. 1 means an API error. The API JSON error body is on stderr, and the process exits 1. On HTTP 429 that body includes retry_after_seconds and resets_at when the API sends them. 2 means a usage error, such as a missing flag or a rejected base URL. 3 means the network call failed.

Sample output:

```json
{
  "error": {
    "type": "rate_limit",
    "code": "rate_limited",
    "message": "At most 5 inbox waits can run at once for this key.",
    "retry_after_seconds": 1,
    "resets_at": "2026-10-18T15:00:01.000Z",
    "docs_url": "https://cooperemail.com/docs#rate_limited"
  }
}
```

Operator: Avatar 8 LLC (ops@avatar33.com).
