CLI

Scripting & CI

Every command speaks JSON, reads its key from the environment, and never blocks on a prompt when it isn't attached to a terminal.

JSON output

--json prints the raw API response instead of a table. That is the contract to script against — table columns are for humans and may change.

shell
# Every scheduled post, id and time
echoia posts --status scheduled --json | jq -r '.posts[] | "\(.id) \(.scheduledAt)"'

# Account ids for one platform
echoia accounts --json | jq -r '.accounts[] | select(.platform=="instagram") | .id'

# Anything that needs reconnecting
echoia accounts --json | jq -r '.accounts[] | select(.needsReconnect) | .username'

Naming accounts in a script

Do not hard-code --platforms in automation. The moment a second account is connected on that platform the call starts failing — deliberately, because the destination would otherwise be ambiguous. Write the handle instead; nothing has to be looked up first:

shell
echoia post "Shipping today." --accounts instagram:@acme --now

A handle stops resolving if that account is renamed or disconnected — the call fails rather than going somewhere else. When a script must survive a rename, pin the id instead:

shell
IG=$(echoia accounts --json \
  | jq -r '.accounts[] | select(.platform=="instagram" and .username=="acme") | .id')

echoia post "Shipping today." --accounts "$IG" --now

To fan out to every account on a platform, collect them all — one call, one result per account:

shell
ALL=$(echoia accounts --json \
  | jq -r '[.accounts[] | select(.platform=="instagram") | .id] | join(",")')

echoia post "Same announcement, every brand." --accounts "$ALL" --at 1h

Environment

VariableEffect
ECHOIA_API_KEYThe API key. Takes precedence over the stored one, so CI never needs a login step.
ECHOIA_BASE_URLPoint at another API host.
ECHOIA_CONFIG_DIRWhere config.json lives.
NO_COLORDisable colour. Already off automatically when stdout is not a terminal.

Exit codes and errors

  • 0 on success, 1 on anything else. Errors go to stderr, so --json on stdout stays parseable.
  • set -e is enough to stop a script on the first failure.
  • A 403 names the scope the token is missing; a 429 says whether it is a rate limit (retry) or an exhausted monthly quota (retrying will not help).

--now does not ask

--now publishes on the spot: the flag is the confirmation, and there is no prompt to catch a typo. --yes is still accepted so older scripts keep running, but it has nothing left to skip.

reply is the exception and still confirms, because a reply goes out to one particular person under your name. Pass --yes there when you mean it.

GitHub Actions

Store the key as a repository secret. Give the token only the scopes the job needs — a job that schedules posts does not need publish.

.github/workflows/announce.yml
name: Announce release

on:
  release:
    types: [published]

jobs:
  post:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/setup-node@v4
        with:
          node-version: 20

      - name: Schedule the announcement
        env:
          ECHOIA_API_KEY: ${{ secrets.ECHOIA_API_KEY }}
        run: |
          set -e
          ACCOUNTS=$(npx echoia-cli accounts --json \
            | jq -r '[.accounts[] | select(.needsReconnect | not) | .id] | join(",")')

          npx echoia-cli post "${{ github.event.release.name }} is out." \
            --accounts "$ACCOUNTS" \
            --at 1h

Reviewing before it goes out

The safest automation writes a draft and lets a human release it. Omit --at and --now — nothing is published, and the post is waiting in the Posts tab.

shell
echoia post "$(cat announcement.txt)" --accounts "$ACCOUNTS" --json \
  | jq -r '"draft \(.post.id) → " + ([.accounts[].account] | join(", "))'

Pacing a batch

The API rate-limits per token, and X writes draw on a separate monthly pool. When looping over many posts, publish sequentially and let a failure stop the batch rather than retrying blindly:

shell
set -e
while IFS= read -r line; do
  echoia post "$line" --accounts "$ACCOUNTS" --at 30m
  sleep 2
done < posts.txt
Was this page helpful?