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 --jsonprints JSON;zilfu --json healthsilently ignores the flag and prints the table, andzilfu posts --json list --space 1drops the--jsonthe same way. A flag that takes a value fails louder: the value is read as a command name, sozilfu --token abc whoamiandzilfu posts --space 1 listboth 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 listreturns 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 listandaccounts listare 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
--jsoninto something likejq— 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.