Scripting and automation
The CLI is built to run without anyone watching: in shell scripts, cron jobs, CI pipelines, and AI agents. This page covers what you can rely on when you do that: output formats, exit codes, errors, retries, and how to test a command safely.
stdout is data only
Every command writes its result to stdout. Progress messages, prompts, warnings and errors go to stderr. Piping a command's output always gives you just the data.
Output formats
Choose the format with -o (or --output, or the MIXPOST_OUTPUT environment variable).
When you don't choose one, the CLI uses table on a terminal and json everywhere else. A command
in a pipe or a script gets JSON without you asking for it.
| Format | Output |
|---|---|
table | aligned columns, for reading |
json | the API response, unchanged |
yaml | the API response, as YAML |
csv | the table columns, comma-separated |
tsv | the table columns, tab-separated |
id | one identifier per line (the uuid, or the numeric id when there's no uuid) |
template | your own Go template, set with --template |
mixpost post list # table on a terminal
mixpost post list | jq -r '.data[].uuid' # JSON in a pipe
mixpost post list -o id | xargs -n1 mixpost post get
mixpost post list --all -o csv --no-headers > posts.csv
mixpost post list -o template \
--template '{{range .data}}{{.uuid}} {{.status}}{{"\n"}}{{end}}'
- JSON is passed through unchanged, so you also get fields the CLI doesn't know about. Add
--compactto print it on one line. - Tables are a summary. For analytics in particular, the table shows the main figures, and JSON has everything, including charts.
--no-headersremoves the header row fromtable,csvandtsv.
Exit codes
Each kind of failure has its own exit code, so a script can react to it without parsing error messages:
| Code | Meaning |
|---|---|
0 | success |
1 | other failure |
2 | wrong command, flag or configuration (for example, no workspace set) |
3 | 401 not authenticated: the key is missing, wrong or expired |
4 | 403 forbidden: your role doesn't allow this |
5 | 404 not found |
6 | 422 validation failed, including unmet platform requirements |
7 | 429 rate limited |
8 | 5xx server error |
9 | network failure or timeout |
10 | cancelled at a confirmation prompt |
mixpost post get "$uuid" > /dev/null
case $? in
0) echo "exists" ;;
5) echo "gone" ;;
*) echo "something else went wrong" >&2; exit 1 ;;
esac
Errors
When the output format is json, yaml or template (which includes every run in a pipe), errors
are written to stderr as JSON. A script or an agent can read them the same way it reads data:
{
"error": {
"message": "Validation errors",
"status": 422,
"exit_code": 6,
"errors": { "versions.0.content": ["The content is required."] }
}
}
errors lists every field that failed validation, with the reasons. With table, csv, tsv or id
output, the error is printed as plain text instead.
Confirmations
Commands that delete things ask before they act: post delete --permanent, media delete,
media folder delete, tag delete, mention delete and auth logout.
When there is no terminal to ask on, these commands refuse to run instead of assuming you agree.
Pass --yes (or -y) to confirm in a script:
mixpost post delete "$uuid" --permanent --yes
Test a command with --dry-run
--dry-run prints the request the command would send (method, URL, headers and body) and stops
without changing anything:
mixpost post create --text "Launch" --account linkedin --at "2026-09-10 15:00" --dry-run
Lookups still run during a dry run. Turning -w acme or --account linkedin into an id needs a read
request, and that is what makes the printed request accurate.
To see every request and response of a real run, add --debug. The trace is written to stderr.
Retries and timeouts
Requests that fail because of rate limits, network errors or server errors are retried, 2 times by
default. Change this with --retries.
Requests that create something (POST) are only retried after a 429, because a 429 means the
server didn't process the request. This way a retry never creates a post twice.
Each request times out after one minute. Change this with --timeout, for example --timeout 2m.
Pagination
List commands return one page by default.
--allfetches every page.--limit Nstops after N items.--pageand--per-pagefetch a specific page.
mixpost post list --status failed --all -o id
With mixpost raw, use --paginate or --limit to do the same for any list endpoint.
CI and cron
You don't need a configuration file in CI. Set the key (and the address, for a self-hosted instance)
as environment variables from your secrets, and pass the workspace with -w:
name: Announce release
on:
release:
types: [published]
jobs:
announce:
runs-on: ubuntu-latest
steps:
- name: Install Mixpost CLI
run: curl -fsSL https://raw.githubusercontent.com/inovector/mixpost-cli/main/install.sh | sh
- name: Queue the announcement
env:
MIXPOST_PERSONAL_KEY: ${{ secrets.MIXPOST_PERSONAL_KEY }}
MIXPOST_URL: ${{ vars.MIXPOST_URL }} # leave empty for Mixpost Cloud
run: |
mixpost post create -w "${{ vars.MIXPOST_WORKSPACE }}" \
--text "${{ github.event.release.name }} is out: ${{ github.event.release.html_url }}" \
--account twitter --account linkedin --queue
A weekly report from cron, every Monday at 7:00:
# m h dom mon dow command
0 7 * * 1 mixpost analytics summary -w acme --period last_week -o json > ~/reports/weekly.json
Create a separate personal key for each pipeline, with an expiry date. If one leaks, you can revoke it without affecting anything else. Keys are listed on the API page in the user menu.
Recipes
Empty the trash:
mixpost post list --trashed --all -o id | xargs mixpost post delete --permanent --yes
Schedule a post for each account's best time:
mixpost analytics best-times --account linkedin --tz Europe/Chisinau -o json
# take the time from the output, then:
mixpost post create --text "Weekly tips" --account linkedin \
--at "2026-10-20 09:00" --tz Europe/Chisinau
Export last month's analytics to a file:
mixpost analytics summary --period last_month -o json > analytics-$(date +%Y-%m).json
AI agents
Agents that run shell commands, such as Claude Code, Codex, Cursor or Gemini CLI, can use the CLI without any setup beyond logging in. The design choices above are what make that work well:
- Built-in help is enough to learn the CLI. An agent can start with
mixpost --helpandmixpost post --help, and every command's help includes examples. - JSON by default. An agent's commands don't run on a terminal, so it gets JSON without asking.
- Errors as data. Errors are JSON on stderr, with the exact field that failed and why, so the agent can fix the command and try again.
- Natural names. The agent can write
--account linkedinor-w acmeinstead of looking up ids first. - Safe by default. Deletes need an explicit
--yes, and--dry-runshows what would happen first.
A typical instruction you can give an agent:
Using the
mixpostCLI, write three posts about our new media folders feature for LinkedIn and X, and schedule them for the best times next week. Show me the drafts before you schedule them.
To keep an agent from changing anything, give it a key that belongs to a user with the Viewer role. It can still read posts and analytics.
If your agent runs in a chat app rather than a shell, such as Claude or ChatGPT on the web, use the MCP server instead.