CLI

Commands

Every Zilfu CLI command, the flags it takes, and what its output does and does not include.

Updated Jul 27, 2026

Seven commands. None of them write anything through the API. login and logout do write and delete your local credentials file — see Get started for those. The other five each make one API call and print the result.

There are no positional arguments anywhere in the CLI. Every input is a flag.

Global flags

Flag Effect
--token <token> Use this token for the request, overriding ZILFU_TOKEN and the stored config
--base-url <url> Send the request to this API base URL, overriding ZILFU_BASE_URL and the stored config
--json Print pretty JSON to stdout instead of the human-readable table (or, for whoami and health, the field list)

All three are accepted by whoami, health, spaces list, accounts list, and posts list — and only by those.

login defines its own flags (--web, --token, --base-url) and takes no --json. logout takes no flags at all: it deletes the config file and prints one line. Neither command errors on a flag it doesn't know — the parser accepts unknown flags silently — so zilfu logout --json is accepted, logs you out normally, and simply ignores the flag — there is no JSON output and no warning that the flag was meaningless.

Global flags must come after the leaf subcommand. zilfu health --json prints JSON; zilfu --json health silently ignores the flag and prints the table, and zilfu posts --json list --space 1 drops the --json the same way. A flag that takes a value fails louder: the value is read as a command name, so zilfu --token abc whoami and zilfu posts --space 1 list both print the usage block, Unknown command abc / Unknown command 1, and exit 1.

A group name on its own is not a command. zilfu spaces prints the usage block, then No command specified. on stderr, and exits 1 — it does not fall back to list. Write the leaf out every time.

zilfu whoami

The user the token belongs to.

zilfu whoami
id     7
name   Ada Lovelace
email  ada@example.com

Three fields, and that is the whole record. Use it to confirm which account a token belongs to before you trust a script with it.

zilfu health

The only command that does not require credentials — it runs fine with no token at all. It still resolves one if you have it, and sends it: a stored token, ZILFU_TOKEN, or --token is attached as a Bearer header on the health request like on any other command.

zilfu health
status  ok

That is the entire healthy response. There is no "degraded" status line: when the API is unhealthy it answers 503, and the CLI turns any non-2xx into an error. So an unhealthy API prints nothing at all on stdout, even with --json, and instead writes to stderr and exits 1:

Error 503: Zilfu API 503 Service Unavailable

That is how you tell unhealthy from unreachable: a bad DNS name, a refused connection, or a malformed --base-url all report status 0, because no HTTP response came back at all. A bad DNS name or a refused connection prints Error 0: fetch failed; a --base-url that isn't a URL prints Error 0: Failed to parse URL from <value>/health.

zilfu spaces list

The command that gives you the space id everything else needs.

zilfu spaces list
id  name            timezone
--  --------------  -----------
1   Acme Marketing  Europe/Rome
2   Nova Labs       UTC

The timezone column is the space's own timezone, used by the app when you pick a time in the UI. It does not affect this CLI's output — every timestamp the CLI prints is UTC. Not paginated: every space you can access is listed.

Passing --space

--space is required on exactly two commands: accounts list and posts list. It takes a positive integer space id from zilfu spaces list.

There is no default space, no ZILFU_SPACE environment variable, and nothing stored in the config file. You pass it on every invocation.

A value that isn't a positive integer fails before any request is made:

zilfu posts list --space abc
Invalid space id: abc

0, negative numbers, and decimals are rejected the same way. Omitting the flag entirely fails at the parser instead, with the usage block and Missing required argument: --space.

One trap worth knowing: the parser takes the next token as the flag's value, whatever it is. zilfu posts list --space --token abc fails with Invalid space id: --token.

zilfu accounts list

The social accounts connected to a space.

zilfu accounts list --space 1
id  social     display_name  handler   is_active
--  ---------  ------------  --------  ---------
12  instagram  Acme          acme      true
13  linkedin   Acme Inc.     acme-inc  true
14  threads    Acme          acme      false

social is one of threads, x, instagram, linkedin, tiktok, facebook, pinterest. is_active prints as true or false. Not paginated.

zilfu posts list

Top-level posts in a space, newest scheduled time first.

zilfu posts list --space 1
id   status     scheduled_at                 published_at
---  ---------  ---------------------------  ---------------------------
478  draft
481  scheduled  2026-08-01T09:00:00.000000Z
477  5          2026-07-30T08:00:00.000000Z
479  published  2026-07-26T17:30:00.000000Z  2026-07-26T17:30:04.000000Z

Timestamps are UTC, not the space's timezone. Empty cells are nulls — a draft has neither a scheduled nor a published time, and rows with no scheduled time sort to the top.

Post statuses

Posts are stored as numeric codes. The table prints a label for each; --json gives you the raw number.

Code Label printed
0 scheduled
1 published
2 error
3 draft
4 processing
5 5

Code 5 is a post pending approval. The CLI has no label for it and prints the bare number, as in row 477 above. Any future status behaves the same way.

posts list returns only the first page. The API paginates this endpoint at 15 posts per page by default, the CLI sends no paging parameters, and it discards the pagination envelope — so you get the 15 most recent posts with nothing in the output indicating that more exist. This is posts-only; spaces list and accounts list are not paginated.

There are no filtering or paging flags, even though the API supports filtering by status, account, and date range, and accepts a page size up to 200. To filter or to reach older posts, call the API directly (Quickstart), or pipe --json into something like jq — see Scripting.

The tables are a projection

Each table shows a fixed set of columns chosen by the CLI, not everything the API returned:

Command Dropped from the table
spaces list created_at, updated_at
accounts list profile_url, avatar, disconnected_at
posts list everything except the four columns — content, metadata, type, order, account_id, cluster_id, parent_id, user_id, created/updated timestamps, analytics and analytics_metrics, and the nested account, media, children, scheduled_comments and review objects

Only --json is lossless. If a field you need isn't in the table, it is very likely already in the JSON — check there before reaching for the REST API.