Command reference
Every Zanfia CLI command, grouped by resource in the order the CLI registers them: auth, products, offers, checkout settings, site, emails, OTO, bumps, courses, certificates, consultations, newsletters, communities, conversations, clients, campaigns, segments, forms, testimonials, brand, team, orders, subscriptions, stats, discounts, referrals, workflows, funnels, pages, social, media.
All commands support --json for machine-readable output; --api-key <key> overrides the stored credential for one call and --verbose logs request/response details. These global flags work at any position (zanfia --json products list and zanfia products list --json are equivalent). Money amounts are minor units (1999 = 19.99) unless a flag says otherwise. Every command maps onto a Workspace API route, so anything here also works as raw HTTP. Every leaf command is listed below: one row each; <command> --help prints the full flag list, and <command> --help --short prints only the command names (subcommands indented, no descriptions): handy for a quick map of a group or of the whole CLI (zanfia --help --short).
JSON-taking options and the --json output flag
Wherever a command takes a JSON value (--json, --patch, --config, --chapters, …) both
--json <file|inline> and --json=<file|inline> work. Every such option accepts inline JSON or a path
to a .json file. On commands where --json carries a request body, switch to machine-readable output
with -o json (or put the global flag before the command: zanfia --json offers update <id> --json body.json).
Auth
zanfia login [--workspace <id|slug>] [--address <creator-platform-url>] [--api-url <url>]
Browser sign-in (OAuth consent page): pick the workspace there (Owner or Admin;--workspacepreselects it) and the CLI stores a 90-day token for it, which becomes the active login. Logins of other workspaces stay stored.--addresssigns in as a community member of that creator's platform: Community API onlyzanfia workspace list
The workspace logins stored on this device;*marks the active onezanfia workspace use <id|slug>
Make another stored workspace login the active one, without signing in again--workspace <id|slug>on any command
Use another stored workspace login for that one command (--api-keyandZANFIA_API_KEYstill win)zanfia whoami
The active workspace and identity, and where the key comes from (same asauth whoami)zanfia register [--email <e>] [--name <n>] [--lang en|pl] [--access-code <c>] [--code <c>]
Create a new creator account and log the CLI in: opens the signup page in your browser (a quick security check), then logs in likezanfia loginzanfia join <handle> [--email <e>] [--name <n>] [--code <c>]
Join a public community from the terminal (emailed code) and save a member key: no account or browser neededzanfia logout [--all]
Forget the active login (the newest remaining one becomes active);--allforgets every stored loginzanfia auth status
Show what the CLI has stored: path, credential source, expiry, API URLzanfia auth set-key <key> [--api-url <url>]
Store an API key as the credential (member keys: pass the creator's api-community URL)zanfia auth whoami
The workspace (or community member) identity behind the current credential
Products
products list
List products in the workspaceproducts get <id>
One productproducts create --name <n> --type <course|newsletter|community|…> [--description | --description-file <path>] [--free | --amount <major> --currency <c> [--period one_time|monthly|yearly]] [--cover <pathOrUrl>] [--file <path>…] [--language pl|en]
New product; community/newsletter types seed their full content.--free/--amountalso seed a default offer (amount in major units here, e.g.49.99). One-shot extras:--coversets the cover image (a local file is uploaded to the media library first; an https URL is used as-is),--file(repeatable) uploads local files and attaches them to a downloads product,--description-filereads the description from a file,--language pl|ensets the product's own language (buyer-facing pages and e-mails; omitted = the platform's language) and picks the--amountcurrency when--currencyis omitted (pl → pln, else usd)products update <id> [--name] [--description] [--visibility <public|unlisted|private>] [--language pl|en|none] [--buyer-files on|off]
Rename / re-describe;--visibilitysets where buyers find it once published:public(store + product page),unlisted(product page by link only),private(checkout links only);--language pl|en|nonesets the product's language (noneclears it: the platform's language applies);--buyer-files on|offsets "Files for buyers" (the product's file set fromfiles pushopen to its buyers, or hidden; the files stay saved)products publish <id>
Status → published (live for customers)products draft <id>
Status → draft (hidden from customers)products archive <id>
Status → archived (not a delete)products duplicate <id> [--name <name>] --confirm
Copy a product as a draft (settings, offers with new prices, content) in a background run;--namenames the copy (default: the name + "(copy)"). Community, knowledge base and newsletter products are refused (422)products prices list <pid>
Prices collected across the product's checkoutsproducts prices create <pid> --amount <minor> --currency pln|usd|eur|gbp [--label]
Add a one-time paid price (creates a default checkout if needed; gateway sync is server-side)products appointment get <pid>
Appointment config of a consultation / service / event productproducts appointment set <pid> [--config=<json|file>] [--booking-method native|external] [--booking-url] [--duration <min>] [--location] [--deliverable …] [--delivery-time <n> --delivery-unit days|weeks] [--revision-rounds <n|unlimited>] [--brief-method] [--brief-url] [--event-mode online|offline] [--starts <iso>] [--ends <iso>] [--capacity <n>] [--show-on-success on|off] [--booking-questions <json|file>]
Update the appointment config: reads the current one first, so partial flags are fine; a first-time setup needs every required field (or--config).--booking-questions(consultations) REPLACES the booking-window questions with a JSON array of{ id?, label, type: shortText|longText|phone|date, required? }: keep the ids fromappointment get --json, a question without one is new;[]removes them allproducts sales-page get <pid>
The page the product's address shows (its Product page), or the themed product pageproducts sales-page set <pid> --page <pageId> | --themed
Make a PUBLISHED page of the workspace the product page, or put the themed page back (key withproducts:write+pages:read)products after-purchase get <pid>
Thank-you page + email settingsproducts after-purchase set <pid> [--settings=<json|file>] [--thank-you-type defaultPage|redirect] [--redirect-url] [--email-buyer-purchase on|off] [--notify-owner-purchase on|off] [--email-buyer-failed on|off] [--notify-owner-failed on|off]
Update after-purchase settings (partial flags merge);--settingsfor the full object with custom thank-you / email contentproducts images set <pid> --image <url> [--image <url> …]
Replace the cover images (full replace; https URLs already hosted: upload to the media library first)
Plans and bundling (products plans)
Tiers with included products. Invariants are server-enforced: unique plan ids, at most one default, one level of bundling, and a plan still sold by active prices cannot be removed.
products plans get <pid>
The product's plans: id, name, active, default, included productsproducts plans set <pid> --json <file|inline>
Full replace of the plans array (or{plans: [...]}): send every plan you keep, with its id; plans without an id are createdproducts plans count-clients <pid> [--plan <planId>]
Active customers whose effective plan is--plan(omit it to size a not-yet-saved first/default plan)products plans set-client-plan <pid> --plan <planId> (--client <wcid> | --email <e>)
Move one client's grant to another plan: billing-neutral; community membership is reconciled server-sideproducts plans backfill <pid> --community <communityProductId> --confirm
Eagerly create community member docs for every active customer whose plan includes the community
Downloads content (products files)
products files get --product <pid>
Description presence + file table of a downloads productproducts files set-content --product <pid> (--content <md> | --content-file <path>)
Replace the Markdown description shown above the file listproducts files add --product <pid> (--url <url> --name <n> [--media-id] [--size] | --files=<json|file>)
Attach one file, or several via a JSON array of{url, name, mediaId?, size?}; already-attached mediaIds are skippedproducts files remove <uid> --product <pid>
Detach a file by its uidproducts files reorder <uid...> --product <pid>
Reorder the file list: pass every current uid in the new order
Knowledge base (products kb)
Address a knowledge-base product by its product id. Writes are committed edits: they also clear the dashboard editor's unpublished draft for the touched article.
products kb get <pid>
Categories (tree keys = the--categoryids) and every article, drafts includedproducts kb tree set <pid> --json <file|inline>
Replace the whole order:{"categories": [{"categoryId", "articleIds": []}, …]}, every category and article; a concurrent edit is refused (tree-changed); re-fetch withproducts kb get --jsonand retryproducts kb article get <pid> <aid>
One article, published fields, full bodyproducts kb article create <pid> --title <t> --category <cid> [--content | --content-file] [--status draft|available|unavailable]
New article (defaultdraft)products kb article update <pid> <aid> [--title] [--status] [--category <cid>] [--content | --content-file] [--cover <mediaId>:<url> | --clear-cover]
Edit an article;--coversets the cover image from the media library (:<url>for an external image)products kb article delete <pid> <aid> --confirm
Permanent. To hide instead, set--status unavailableproducts kb category add <pid> --name <n>
New root categoryproducts kb category rename <pid> <cid> --name <n>
Rename (the articles' denormalized category name follows)products kb category delete <pid> <cid> [--delete-articles] --confirm
Permanent; a non-empty category needs--delete-articles
Buyer notifications (products notifications)
A short message (a title, up to 200 characters and an optional link on your own platform) to everyone with access to one product right now: a push in the mobile app, a bell item on the portal and, with --send-email, an e-mail. Run preview first: it sends nothing and shows how many people the message reaches; pass that count to send as --confirmed-recipient-count (more people with access by then = 409, preview again). One send per product per hour and a daily cap per workspace (429); a send cannot be recalled, and list shows the delivery counts a little later.
| Command | Purpose |
|---|---|
products notifications preview <productId> [--link <value>] [--send-email|--no-send-email] | Preview a notification to a product's buyers |
products notifications send <productId> --title <value> --body <value> [--link <value>] [--send-email] --confirmed-recipient-count <n> [--send-id <value>] --confirm | Notify a product's buyers |
products notifications list <productId> [--limit <n>] | List a product's buyer notifications |
Offers (checkouts)
An offer is a checkout of a product; <cid> comes from offers list.
offers list [--product <pid>]
Browse offersoffers get <cid> [--full]
One offer with its prices and attached bumps;--fulladds every editable setting (media gallery,theme,deadline,afterPurchase,otochain, form, legal, invoicing, notifications, tracking): the same keysoffers update --jsonaccepts, so you can read, edit and write backoffers create --product <pid> [--free] [--name <n>]
New offer (free offers carry no prices)offers update <cid> [--name] [--slug] [--title] [--description] [--success-url <url|none>] [--language <code|none>] [--show-testimonials on|off] [--show-plan-includes on|off] [--invoice-as-option on|off] [--hide-country-field on|off] [--show-phone-field off|optional|required] [--allow-promotion-code off|subscriptions|all] [--force-b2b on|off] [--show-express-checkout on|off] [--promote-net-prices on|off] [--stripe-tax-behavior inclusive|exclusive] [--multiple-purchases on|off] [--allow-plan-upgrade on|off] [--terms-url] [--privacy-policy-url] [--claim] [--disable-invoice-issuing on|off] [--disable-purchase-email-client on|off] [--tracking-integration-ids <ids|none>] [--json <patch>] …
Edit any checkout-builder setting: display, form & UX, legal, invoicing, notifications, tracking (--helplists all ~50 flags; booleans takeon|off,noneclears). Deep fields (theme,deadline,afterPurchase,paymentMethodsDisplay,checkoutMedia,otochain …) go through--json; explicit flags winoffers publish <cid>
Activate (paid offers need a connected payment gateway)offers unpublish <cid>
Pause: stops new purchasesoffers duplicate <cid> [--with-prices] [--name <n>]
Copy an offeroffers notify-waitlist <cid> [--send --confirm]
"It's back": e-mail the offer's waiting list, once per sign-up. Without--sendit only countsoffers set-default <cid>
Make it the product's default checkoutoffers delete <cid>
Permanent: prices are archived on the gateways and the offer detached; preferoffers unpublishoffers recovery preview <cid> <sessionId>
Who a cart-recovery email would go to and what it would say. Nothing is sentoffers recovery send <cid> <sessionId> --confirm
Sends a real "finish your purchase" email to the visitor who abandoned the session. Preview firstoffers price add <cid> --type one_time|recurring|installments --amount <minor> --currency pln|usd|eur|gbp [--interval day|week|month|year] [--interval-count] [--cycles <n>] [--allow-cancellation] [--trial-days] [--label] [--invoice-name] [--hidden] [--description] [--sale-ends-at <iso> | --sale-days <n>] [--quantity-limit] [--show-available] [--compare-at <minor>] [--access-interval --access-count | --access-until <iso>] [--plan <planId>]
Add a price variantoffers price update <cid> <priceId> [--operation activate|deactivate|hide|unhide|change-amount|edit-details] [--amount <minor>] [--label <text>] [--description <markdown>]
Mutate a price.--label/--descriptionedit the display label and the markdown description under the price (edit-details, implied when--operationis omitted; an empty--description ""clears it); they cannot be combined with another operationoffers price delete <cid> <priceId> [--remove]
Archive (default) or remove entirelyoffers price reorder <cid> --order id1,id2,…
Display order: pass all price idsoffers media list <cid>
The offer's media gallery (checkout images and videos) in display order: index 0 is featuredoffers media add <cid> (--media <mediaId> | --url <url> --type image|video [--thumbnail <url>])
Append a Media Library item (videos must beready; the Bunny embed URL is built for you) or an external URL to the gallery: read-modify-write throughGET ?include=full+PATCH, validated by the same editor schema asoffers update --jsonoffers media remove <cid> <mediaId|url>
Drop a gallery entry by its media id or URL
Checkout settings
Workspace-wide defaults every checkout page reads unless an offer overrides a field itself.
checkout-settings get [--lang pl]
The workspace cart/checkout page settingscheckout-settings update [--lang pl] [--terms-url] [--privacy-policy-url] [--claim] [--terms-content] [--privacy-policy-content] [--google-analytics-id] [--facebook-pixel-id] [--gtm-id] [--tiktok-pixel-id] [--promote-net-prices on|off] [--price-variants-placement belowDescription|aboveDescription|none] [--json <patch>]
Legal links / texts, tracking ids, net prices; deep fields (embeddedForm,checkoutTheme,cartTheme) via--json;noneclears a nullable fieldcheckout-settings consents list
The consent checkboxes buyers see on checkout pages, in display ordercheckout-settings consents upsert <name> --label <t> --required|--optional --apply-to all|'<json>' [--url <url|none>] [--marketing-opt-in|--no-marketing-opt-in] [--json <body>]
Add or edit one consent by its stable name: scope ids are verified against real products/offers (unknown id → 404)
Stripe Tax
The workspace's Stripe Tax settings (Integrations → Payments → Stripe): on / off, the default price mode (inclusive = gross prices, exclusive = net prices with tax added at checkout) and "EU companies pay the net price". Turning it on needs Stripe Tax active in your own Stripe account first. Needs a key with the integrations:read / integrations:write scopes.
| Command | Purpose |
|---|---|
tax get | Read the Stripe Tax settings |
tax set [--enabled|--no-enabled] [--behavior <inclusive|exclusive>] [--eu-companies-pay-net|--no-eu-companies-pay-net] | Change the Stripe Tax settings |
Integrations (conversion tracking)
Managed conversion-tracking integrations (Settings → Integrations → Analytics): Google Tag Manager, Google Analytics 4, TikTok, X, LinkedIn, Microsoft Clarity, Hotjar. Platform aliases: gtm, ga4, clarity, twitter. Assign per offer with offers update --tracking-integration-ids, or workspace-wide with integrations set-defaults. Writes need products:write. The credential-bearing connections below (status, email-clients, meta-capi, manual-payment) need a key with the integrations:read / integrations:write scopes; OAuth providers (Stripe, Meta OAuth, Google, social) and the credential payment gateways stay dashboard-only.
integrations list [--client <platform>]
Every managed integration with its id, platform id, server-side flag and whether it is a workspace default, plus the default id listintegrations add <platform> --name <n> (--container-id | --measurement-id [--api-secret] | --pixel-id [--access-token] | --partner-id [--access-token] | --project-id | --site-id) [--purchase-tag] [--init-checkout-tag] [--add-to-cart-tag] [--trial-started-tag] [--track-purchase on|off] [--track-init-checkout on|off] [--track-add-to-cart on|off] [--track-trial-started on|off]
New integration: ids are pattern-checked and GA4 / TikTok / LinkedIn secrets are verified against the platform before saving; the first integration a workspace connects becomes the workspace default automaticallyintegrations update <platform> <integrationId> [same flags as add]
Edit: only the flags you pass change; omitted secrets stay stored (--api-secret none/--access-token none= client-side only)integrations remove <platform> <integrationId> --confirm
Irreversible: offers and defaults still listing the id simply stop tracking on that platformintegrations set-defaults <ids|none>
Replace the workspace-default integrations (comma-separated ids fromintegrations listorstats conversion-health;noneclears) applied to every offer that inherits defaultsintegrations status
What is connected: email clients, Meta pixel / Conversions API entries, invoicing, payment methods (needsintegrations:read)integrations email-clients connect <ac|kit|mailerlite|mailchimp|getresponse|pipedrive> --url <url> (--api-key <key> | --api-key-stdin | --api-key-env VAR) [--server-prefix <us21>] [--tags-field <f> --lists-field <f>]
Connect (or re-connect) an email-marketing account: the key is verified against the provider, lists + tags fetched, stored in Secret Manager and never printed; prefer-stdin/-envto keep it out of shell history (integrations:write)integrations email-clients disconnect <client> --url <url> --confirm
Irreversible: refused while a live newsletter / automation still uses the account (the error lists the products)integrations email-clients refresh <client> --url <url> [--server-prefix]
Re-fetch the lists + tags with the stored key (not Pipedrive)integrations meta-capi list
Manual Meta pixel / Conversions API entriesintegrations meta-capi add --name <n> --pixel-id <id> [--access-token <t> | --access-token-stdin | --access-token-env VAR] [--track-purchase on|off] [--track-init-checkout on|off] [--track-add-to-cart on|off] [--track-trial-started on|off]
Add a pixel entry; with a token the Conversions API (server-side events) is on: the token is validated against Meta first. The connect-with-Facebook OAuth path stays in the dashboardintegrations meta-capi update <integrationId> [same flags]
Edit: omitted flags keep their value, an omitted token stays storedintegrations meta-capi remove <integrationId> --confirm
Irreversibleintegrations manual-payment set --name <n> (--instructions <text> | --instructions-file <path>)
Configure the manual / wire-transfer payment method (buyers pay by transfer, you approve the order withorders approve)integrations manual-payment remove --confirm
Remove the manual payment method (idempotent)integrations invoices connect <fakturownia|ifirma|infakt|wfirma|ing|stripe> [--id <id>] --name <n> [--url <url>] (--api-key <k> | --api-key-stdin | --api-key-env VAR) [--account-api-key] [--access-key --secret-key --company-id] [--department-id] [--bank-account]
Connect (or edit with--id) an invoicing account: credentials verified with the provider, never printedintegrations invoices disconnect <client> <accountId> --confirm
Irreversibleintegrations payments connect <payu|tpay|p24|paynow|paypal|dodo> --name <n> [--pos-id] [--client-id] [--client-secret] [--second-key] [--safety-key] [--api-key] [--signature-key] [--merchant-id] [--crc-key] [--secrets-env]
Connect a credential gateway: verified with the gateway first;--secrets-envmakes every secret flag take an env-var NAME. Stripe connects in the dashboardintegrations payments disconnect <gateway> --confirm
Irreversible: prices that only offered this gateway become unbuyableintegrations payments tpay-blik <on|off>
Tpay recurring BLIK (BLIK subscriptions)integrations sms twilio connect --account-sid <sid> (--auth-token <t> | --auth-token-stdin | --auth-token-env VAR) [--auth-kind authToken|apiKey --api-key-sid <sid>] [--region us1|ie1|au1] --sender-kind phoneNumber|messagingService --sender <value>
Connect (or replace) Twilio: verified with Twilio firstintegrations sms twilio disconnect --confirm
Secret deletedintegrations sms smsapi connect [--region pl|com] (--api-token <t> | --api-token-stdin | --api-token-env VAR) --sender <name>
Connect (or replace) SMSAPI: verified with SMSAPI firstintegrations sms smsapi disconnect --confirm·integrations sms smsapi refresh-senders·integrations sms smsapi default-sender <name>
Token deleted · re-fetch the approved sender names · pick the sending senderintegrations sms default-provider <twilio|smsapi>
Which connected provider campaigns and automations send through
Site
Your public site: platform settings (title, language, theme, branding, main page, privacy, client portal, customer access policy, customer-link base URL), the live flag, slug and custom domains, the storefront layout and the legal documents. Every site update <section> writes only that section atomically; each accepts --json <file|inline> and the typed flags override keys from the JSON. Slug, domains, activation and legal-document writes require --confirm.
site get
Summary table: slug, domains, active, language, main page, theme, branding flags, cookie consent, access policy, communication base URL:zanfia --json site getfor the full payloadsite update general [--title <t>] [--description <t>] [--language pl|en|default] [--show-page-title on|off] [--json <patch>]
Page title/description, UI language;--jsonalso takesmainPage,mainPageId,mainPageCommunityIdsite update theme [--theme <color>] [--gray-scale <scale>] [--radius <r>] [--style-variant solid|classic] [--from-brand on|off] [--json <patch>]
Accent color, neutral scale, radius, style;--jsonforcustomColors,customCheckoutTheme,typographysite update branding [--logo-url <url|none>] [--logo-dark-url <url|none>] [--favicon-url <url|none>] [--hide-branding on|off] [--json <patch>]
Logos and favicon (URLs, or Media Library refslogo/logoDark/platformFaviconvia--json); hiding the "Powered by Zanfia" badge needs theremove_brandingfeature (403 otherwise)site update main-page --mode sales|dashboard|community [--page-id <id|none>] [--json <patch>]
What the site root renders: in sales mode--page-idputs a custom Published page at the root (404 unknown page, 409 not published)site update privacy --cookie-consent on|off [--json <patch>]
Cookie-consent bannersite update portal [--library-width full|boxed] [--library-layout grid|gallery|list] [--mobile-library-layout <l>] [--library-order newest|recently_purchased|alphabetical|custom] [--custom-order <ids>] [--group-by-type on|off] [--default-view library|community] [--messages on|off] [--client-messages on|off] [--clear] [--json <patch>]
Client-area customization: the whole object is replaced (readsite get --jsonfirst);--custom-ordertakes comma-separated product ids top first and implies the custom order;--group-by-type onshows the "All" view as communities first + a section per type instead of one flat list;--clearresets to defaultssite update communication --base-url app|platformSlug|platformDomain [--domain <host>] [--json <patch>]
Which host the links sent to customers usesite update access-policy --device-limit <n> [--replacement adaptive|always_allow|creator_approval_only] | --clear [--json <patch>]
Anti-sharing device limit per customer and what happens when it is hit;--clear(or limit 0) switches it offsite activate --on|--off --confirm
Publish or unpublish the site (the platform "live" flag)site slug set <slug> --confirm
Change the workspace slug: heavy (re-registers Stripe/Firebase/Vercel hosts, purges caches), every existing link changes; 409slug-takensite domains set <d1,d2,…>|none --confirm
Replace the custom-domain list (bare hostnames); 409domain-taken, 400restricted-domain; then point DNS at Zanfiasite domains verify [<d1,d2>]
Live DNS + TLS check: status per domain, the DNS records still to add, conflicting records, certificate statesite storefront get
Storefront layout: preset, mode, sections table:--jsonfor the full configsite storefront set --json <file|inline>
Replace the whole storefront config (sectionsrequired): edit the output ofsite storefront get --jsonand send it backsite legal get [--language pl]
Terms and privacy policy: URL,/documents/<slug>slug and written content per languagesite legal set [--language pl] [--terms-content <md|file> | --terms-url <url>] [--terms-slug <s>] [--privacy-content <md|file> | --privacy-url <url>] [--privacy-slug <s>] --confirm
Write a document inline (markdown or a file path → served under/documents/<slug>) or link it out (--*-url); the mode is derived from which flag you passsite library-sections list
Sections of the member library ("Step 1", "Bonuses": a heading and a filter pill each) in display order with their products, plus the products in no section (they show under Other). Products only, never bundlessite library-sections create --name <name> [--products <a,b,...>] [--position <n>] --confirm
Add a section (last by default;--positionis 0-based, the name is unique in the workspace); the listed products move into it from any other sectionsite library-sections rename <sectionId> --name <name> --confirm
Rename a section (its id and products stay)site library-sections reorder <sectionIds...> --confirm
Set the section order: pass every section id once, top firstsite library-sections move <productIds...> --to <sectionId|none> --confirm
Move products into a section (they leave any other), or to none (Other)site library-sections replace --sections <json|file> --confirm
Full replace of the ordered list (a JSON array of{id?, name, productIds}): sections left out are deleted and their products go to Other; keep a section by sending its idsite library-sections delete <sectionId> --confirm
Delete a section; its products go back to Other (no product is deleted)
System emails
The emails Zanfia sends to your customers in your name (Site → Emails): per-email subject/headline/layout overrides, the branding frame, and test sends. <key> comes from emails list.
emails list
The customizable system emails with status and effective subjectemails get <key>
One email: subject/headline (EN + PL), layout blocks, variables, link targetsemails set <key> [--subject-en <t|none>] [--subject-pl <t|none>] [--headline-en <t|none>] [--headline-pl <t|none>] [--enable | --disable] [--layout=<json|file>] [--reset]
Customize one email:--layoutreplaces the whole block layout,--disableonly works for optional emails,--resetrestores the stock emailemails branding get
The email branding and the site defaults it inherits fromemails branding set [--logo-url <url|none>] [--accent-color <hex|none>] [--sender-name <n|none>] [--reply-to <e|none>]
Header logo, accent colour, From name, Reply-To:nonefalls back to the site defaultemails test <key> [--lang en|pl]
Queue a test send (saved customization, sample data) to your own address
E-mail template library (emails library)
The reusable templates (Marketing → E-mail templates) that workflow "Send email" steps reference: a different thing from the system emails above. Attachments are picked in the dashboard; from the CLI you can only clear them.
emails library list
The library templates with subject, frame and attachment countemails library get <id>
One template: subject, preheader, sender overrides, attachments, Markdown contentemails library create --name <n> [--subject] [--preheader] [--content <md> | --content-file <path>] [--branded | --plain] [--from-name <n>] [--reply-to <e>]
Create a template:--nameis the internal label, not the subjectemails library update <id> [same flags] [--clear-preheader] [--clear-from-name] [--clear-reply-to] [--clear-attachments]
Partial update: workflows that reference the template send the new version from now onemails library duplicate <id>
Make an independent copyemails library delete <id> --confirm
Permanent: a workflow step that still references it stops sending until it is re-pointed
Sending identity (emails sender)
emails sender get
Verified sending domains, the default From address and the footer postal addressemails sender set-address (--address <line> | --clear)
The footer postal address rendered under the unsubscribe link of every campaign (one line, max 300 chars): required before a campaign can be sentemails sender warmups
Each sending domain's warm-up: new domains send campaigns in hourly batches for a few days; shows the state, why a domain counts as warmed and the cap per houremails sender declare-warmed <domain> --confirm
"I already send from this domain with another tool": switch the warm-up of a verified domain off at once, recorded under your name (campaigns then go out from it without batches); needs thecampaigns.managepermission
Adding, verifying and deleting a sending domain stays in the dashboard (DNS). get and set-address are launch-gated together with campaigns (403 feature_not_available).
One-time offers (OTO)
Reusable post-purchase upsells shown once, full-screen, between payment success and the thank-you step. These commands manage the offer documents only; attach one to a checkout's chain with offers update <cid> --json='{"oto":{"enabled":true,"steps":[{"otoOfferId":"…"}]}}'.
oto list
The workspace's reusable OTO offersoto get <id>
One OTO offer in fulloto create --name <n> --product <pid> --price <priceId> --headline <t> [--product-name] [--description <md>] [--accept-cta <t>] [--decline-text <t>] [--timer-minutes <n|none>] [--json <body>]
New OTO referencing an existing product and price (creators usually add a dedicated discounted price first);mediagallery only via--jsonoto update <id> [same flags] [--json <body>]
Edit content / pricing fields;--timer-minutes noneclears the countdownoto delete <id> --confirm
Permanent; checkoutoto.stepsstill referencing the id are left dangling
Order bumps
bumps list
Order bumps in the workspacebumps create --product <pid> --price <priceId> --name <n> [--description] [--cta] [--image <urlOrMediaId>] [--json <fileOrJson>]
New bump from an existing price (the price must be on the product's default offer).--imagesets the bump box image (https URL or a media library id);--jsonseeds the deepappearanceobjectbumps update <bumpId> [--name] [--description] [--cta] [--product <pid> --price <priceId>] [--image <urlOrMediaId|none>] [--json <fileOrJson>]
Edit, or point the bump at another product/price.--imagesets (ornoneclears) the bump box image;--jsonis a deep patch merged into the request body:appearance(media, backgroundColor, borderStyle, borderColor, borderWidth, ctaPosition, ctaSize, ctaBackgroundColor, ctaTextColor, ctaArrowPosition, ctaArrowColor, ctaArrowAnimation, imagePosition, imagePositionMobile, imageWidth); explicit flags win over--jsonkeysbumps delete <bumpId>
Delete (offers that reference it stop showing it)bumps attach <cid> <bumpId> [--prices id,id]
Show a bump on an offer (omit--prices= all prices; re-scopes if already attached)bumps detach <cid> <bumpId>
Remove a bump from an offer
Courses
Address everything by product id (from products list). Statuses: draft, public, hidden, locked; new modules and lessons start as draft. Lesson content is Markdown, and an update replaces the whole body; prefer --content-file for anything long.
courses get <pid>
Full module/lesson structure with statusescourses analytics <pid> [--learners-limit <n>] [--learners-sort progress-desc|progress-asc|last-activity-desc]
Per-lesson engagement, quiz pass rates and the learner rostercourses transcripts <pid>
Lesson → video/transcript map (read one withmedia transcript <mediaId>)courses module add <pid> --name <n> [--description]
New draft modulecourses module update <pid> <mid> [--name] [--description] [--status] [--unlock-days <n> | --unlock-date <iso> | --clear-unlock] [--require-quiz-pass | --require-all-lessons | --clear-requirement]
Edit a module: name, status, drip unlock, completion gatecourses module delete <pid> <mid>
Permanent, cascades to all its lessons. To hide instead, set--status hiddencourses lesson get <pid> <lid> [--content]
Lesson detail;--contentprints just the Markdowncourses lesson add <pid> --module <mid> --title <t> [--type article|video|externalVideo|task|quiz] [--description] [--video-media <mediaId>]
New draft lessoncourses lesson update <pid> <lid> [--title] [--description] [--status] [--content | --content-file]
Edit a lessoncourses lesson set-video <pid> <lid> --video-media <mediaId>
Bind an uploaded videocourses lesson set-thumbnail <pid> <lid> --media <mediaId> | --url <httpsUrl> | --clear
Lesson thumbnail; video lessons also get it as the player thumbnailcourses lesson set-attachments <pid> <lid> --media <id,id,…> | --clear
Replace the lesson's downloadable attachments with media-library files, in the given order (full list;lesson getshows current uids);--clearremoves allcourses lesson video get <pid> <lid>
Chapters, moments and meta tags stored on the lesson videocourses lesson video set <pid> <lid> [--chapters=<json|file>] [--moments=<json|file>] [--meta-tags=<json|file>] --confirm
Replace the lists you pass (each replaces its whole list; absent lists stay untouched). Chapters[{title, start, end}], moments[{label, timestamp}]in seconds, meta tags[{property, value}]courses lesson chapters generate <pid> <lid> [--locale pl|en] [--out <file>]
AI chapter candidates from the transcript: printed (and optionally written to a JSON file), not saved; apply withcourses lesson video set --chapters=<file>. Uses AI credits (402 = credits exhausted)courses lesson moments generate <pid> <lid> [--locale pl|en] [--out <file>]
AI moment candidates, same flow: apply withcourses lesson video set --moments=<file>courses lesson delete <pid> <lid>
Permanent. To hide instead, set--status hiddencourses quiz list <pid> <lid>
Questions and settings of a quiz lessoncourses quiz add <pid> <lid> --type single|multi|truefalse --question <t> [--option <t> …] [--correct 1,3] [--correct-answer true|false] [--explanation] [--points <n>] | --json <file|inline>
Add a question (choice types: repeated--option+ 1-based--correct; true/false:--correct-answer)courses quiz update <pid> <lid> <qid> [same flags | --json <file|inline>]
Replace a question in fullcourses quiz delete <pid> <lid> <qid>
Permanentcourses quiz reorder <pid> <lid> --order id,id,…
Pass every question idcourses quiz settings <pid> <lid> [--passing-score <pct> | --clear-passing-score] [--max-attempts <n> | --clear-max-attempts] [--time-limit <s> | --clear-time-limit] [--shuffle-questions on|off] [--shuffle-options on|off] [--show-correct-answers on|off] [--require-pass on|off]
Grading, attempts, shuffling, gatingcourses publish <pid> (--module <mid> | --lessons id,id) [--status draft|public|hidden|locked]
Publish a module and all its lessons, or bulk-change the status of selected lessonscourses reorder modules <pid> --order id1,id2,…
Strict permutation: every module idcourses reorder lessons <pid> --module <mid> --order id1,id2,…
Strict permutation: every lesson id in the modulecourses questions list <pid> [--filter unanswered|all|public|private|hidden] [--lesson <lessonId>] [--cursor <cursor>]
Learners' questions under the lessons, newest first, each with its replies (private ones too); default filterallcourses questions reply <pid> <questionId> (--content <text> | --content-file <path>) --confirm
Reply as the course team: the learner sees the reply under the lesson and is notifiedcourses questions status <pid> <entryId> --status published|hidden --confirm
Hide a question or a reply from learners (the team still sees it), or publish it againcourses questions accept <pid> <questionId> (--reply <replyId> | --clear) --confirm
Mark a reply as the answer to its question;--clearremoves the mark
Certificates
Course-completion certificates: issue them for course clients, withdraw or reinstate a credential, delete a revoked one for good, resend the email, and manage designer templates. Certificate ids come from certificates list; template ids from certificates templates list.
| Command | Purpose |
|---|---|
certificates list [--product <value>] [--limit <n>] [--cursor <value>] | List certificates |
certificates issue <productId> --client <a,b,...> [--only-completed|--no-only-completed] [--template <value>] [--reissue-revoked|--no-reissue-revoked] --confirm | Issue certificates (async) |
certificates revoke <certificateId> --confirm | Revoke a certificate |
certificates reinstate <certificateId> | Reinstate a certificate |
certificates delete <certificateId> --confirm | Delete a revoked certificate |
certificates resend <certificateId> --confirm | Resend the certificate email |
certificates templates list | List certificate templates |
certificates templates create --name <value> [--based-on <value>] [--design-config <json|file>] | Create a certificate template |
certificates templates get <id> | Certificate template detail |
certificates templates update <id> [--name <value>] [--template-json <json|file>] [--design-config <json|file>] [--default|--no-default] | Update a certificate template |
certificates templates delete <id> --confirm | Delete a certificate template |
certificates settings get <productId> | A product's certificate settings |
certificates settings update <productId> [--auto-issue|--no-auto-issue] [--template <value>] [--custom-text <value>] [--validity-months <n>] [--code <value>] [--date-mode <completion|training_date|issue_date>] [--training-date <value>] [--claim|--no-claim] [--claim-from <value>] | Update a product's certificate settings |
Consultations
Consultations booked in the Zanfia calendar. Without --product <productId> (from products list) both commands cover every consultation; pages hold 50 rows by default, at most 100.
consultations bookings [--product <productId>] [--from <date>] [--to <date>] [--status booked|cancelled|done] [--limit <n>] [--cursor <cursor>]
Booked sessions starting in a date range, earliest first, with the client's name and e-mail.--fromtakes ISO orYYYY-MM-DD(default today 00:00 UTC);--tois exclusive (default--from+ 30 days, at most 366 days)consultations without-booking [--product <productId>] [--limit <n>] [--cursor <cursor>]
Buyers with active access to a consultation who never booked a session (a cancelled booking does not count); external-link consultations are skipped
Newsletters
Address a newsletter product by its product id. An issue has two visibilities: --status public|hidden (the private members-area archive) and --landing public|hidden|locked (the public landing page). Edits are staged on a draft and only reach readers on draft publish.
newsletters issues list --product <pid> [--limit] [--cursor]
Archived issues, hidden includednewsletters issues create <pid> --subject <s>
Manual issue ("write a post without sending"): hidden, article-first, empty contentnewsletters issues update <iid> --product <pid> [--status public|hidden] [--landing public|hidden|locked] [--render-mode snapshot|article] [--thumbnail <url>|none]
Visibility, which representation readers see (snapshot= the sent email,article= the markdown), thumbnailnewsletters issues copy <pid> --issue <id,id> --to <pid,pid> --confirm
Copy issues into other newsletter products; non-newsletter targets are reported as skippednewsletters issues draft save <pid> <iid> [--subject <s>] [--content <markdown|file>] [--clear-subject] [--clear-content]
Stage subject / markdown content on the draft;--contenttakes inline markdown or a file pathnewsletters issues draft publish <pid> <iid> [--public] --confirm
Promote the draft into the published issue (edited content switches render mode toarticle; the slug is minted on first publish);--publicalso makes the issue publicnewsletters issues draft discard <pid> <iid> --confirm
Drop the staged edits; the published issue stays as isnewsletters issues delete <iid> --product <pid> --confirm
Permanent. To hide instead, set--status hiddennewsletters settings update --product <pid> [--archive-enabled on|off] [--archive-access disabled|unlimited|from_subscribe_date] [--publication-delay <days|none>] [--default-status] [--default-visibility] [--archive-inbox=<json|file>] [--double-opt-in none|external] [--client-tag <tag>…]
Archive, archive inbox, double opt-in and subscriber tags
Communities
Address by community id (from communities list). You act as yourself: channel access and moderation rights are resolved per request. Posts and comments live in feed channels; content is Markdown. Writing requires a member profile in the community, even for workspace staff. Staff-only commands are marked.
communities list
Your communities (with the backing product id)communities channels <cid>
Channels you can see, with your access levelcommunities posts <cid> <chid> [--limit] [--cursor]
Posts of a feed channel, newest firstcommunities search <cid> <query> [--limit]
Full-text search over the published posts you can readcommunities chat messages <cid> <chid> [--thread <messageId>] [--limit] [--cursor]
Chat messages, newest first (deleted show as tombstones);--threadlists a thread's repliescommunities chat send <cid> <chid> (--content <t> | --content-file <path>) [--reply-to <messageId>]
Send a chat message or a thread replycommunities chat edit <cid> <chid> <messageId> (--content | --content-file) [--thread <parentId>]
Edit an own messagecommunities chat delete <cid> <chid> <messageId> [--thread <parentId>]
Soft-delete an own message (tombstone; threads survive)communities channel create <cid> --name <n> --type feed|chat|link|support [--description] [--group <gid>] [--url <url>] [--readonly]
New channel (--urlrequired for link channels;--readonly= announcement mode)communities channel update <cid> <chid> [--name] [--description] [--url] [--readonly true|false]
Edit a channelcommunities channel delete <cid> <chid>
Delete a channel and its content. Cannot be undonecommunities group create <cid> --name <n> [--description]
New sidebar sectioncommunities group update <cid> <gid> [--name] [--description]
Edit a sectioncommunities group delete <cid> <gid>
Delete a section. Cannot be undonecommunities move <cid> (--channel <chid> | --group <gid>) (--into-group <gid> | --to-root | --before <id> | --after <id>)
Reorder / re-parent the sidebarcommunities post get <cid> <postId> [--content]
Post detail;--contentprints just the Markdowncommunities post create <cid> <chid> --title <t> (--content <md> | --content-file <path>)
New post in a feed channelcommunities post update <cid> <postId> [--title] [--content | --content-file]
Edit a postcommunities post delete <cid> <postId>
Soft delete (author or moderator)communities post pin <cid> <postId>
Pin to the top of its channel (staff)communities post unpin <cid> <postId>
Unpin (staff)communities comments <cid> <postId> [--limit] [--cursor]
Comments, oldest first; replies carryparentCommentIdcommunities comment create <cid> <postId> (--content <md> | --content-file) [--reply-to <commentId>]
Comment or threaded replycommunities comment update <cid> <commentId> (--content | --content-file)
Edit a commentcommunities comment delete <cid> <commentId>
Soft delete (author or moderator)communities support queue <cid> [--status open|in_progress|pending_customer|resolved|closed] [--limit]
The support-ticket queue (moderators)communities support mine <cid> [--status]
Your own ticketscommunities support show <cid> <ticketId>
One ticket with its message threadcommunities support create <cid> --channel <chid> --subject <s> (--message <md> | --message-file <path>)
Raise a ticket in a support channelcommunities support reply <cid> <ticketId> (--content <md> | --content-file)
Reply on a ticketcommunities support status <cid> <ticketId> <status>
Change a ticket's statuscommunities members <cid> [--role <roleId> | --disabled true|false | --email-prefix <p>] [--limit] [--cursor]
Members list (staff); the three filters are mutually exclusivecommunities member set-roles <cid> <memberId> <roleId...>
Fully replace a member's role set (staff)communities member disable <cid> <memberId>
Reversible soft-ban (staff)communities member enable <cid> <memberId>
Re-enable a disabled member (staff)communities member mute <cid> <memberId> --minutes <n>
Timeout for 1..40320 minutes (staff)communities member unmute <cid> <memberId>
Lift an active mute (staff)communities settings get <cid>
The settings document (null= defaults apply) (staff)communities settings update <cid> [--patch=<json|file>] [--events-enabled on|off] [--direct-messages on|off] [--content-visible-from-join-date on|off] [--gamification on|off] [--auto-hide-after-reports <n|off>]
Patch settings: JSON patch keys:uploadVideo,membersDisplay,viewStyle,displayName,icon,publicAccess,gamification,moderation,assistant,digest, …; nested blocks in the patch replace their sub-object, the toggle flags merge (staff)communities roles <cid>
Roles in position order (Owner first)communities role create <cid> --name <n> [--color <hex>] [--icon] [--position <n>] [--permissions=<json|file>] [--content-access=<json|file>]
New custom role (staff)communities role update <cid> <roleId> [--name] [--color] [--icon] [--permissions=<json|file>] [--content-access=<json|file>]
Edit a role: the built-in Owner role is immutable (staff)communities role delete <cid> <roleId> --confirm
Delete a role: holders fall back to Member (staff)communities role reorder <cid> <roleId...>
Pass every role id in the new order, Owner first (staff)communities events list <cid> [--from <iso>] [--to <iso>] [--exclude-cancelled] [--limit]
Calendar events, soonest firstcommunities events create <cid> --title <t> --start <iso> --end <iso> --timezone <iana> --location=<json|file> [--type liveCall|workshop|meetup|conference|retreat|other] [--all-day] [--description] [--json <file>]
New event (staff);--location={"type":"online","url":…}or{"type":"inPerson","address":…}communities events update <cid> <eventId> [same flags] [--all-day on|off] [--status scheduled|cancelled] [--json <file>]
Edit / cancel / restore an event (staff)communities events delete <cid> <eventId> --confirm
Delete an event (staff)communities join-requests list <cid>
People waiting to join a community that approves members by hand: pending requests first (with their answers and the AI hint), then declined ones. Workspace staff with member management; member keys get 403communities join-requests approve <cid> <requestIds...> --confirm
Approve requests: the normal join runs (welcome e-mail with the login link, welcome DM, notifications); up to 20 ids per call, repeats change nothingcommunities join-requests decline <cid> <requestIds...> --confirm
Decline requests quietly (a neutral e-mail, no reason given); up to 100 ids per call, repeats change nothingcommunities digest list <cid>
The weekly digest e-mail to members: this week's draft with the preview of what members get, then the earlier weekscommunities digest approve <cid> [--week <weekKey>] --confirm
Approve the week (nothing goes out until the owner or an admin approves): the digest is sent Thursday 09:00, at once when approved later that Thursday; repeats never send twicecommunities digest skip <cid> [--week <weekKey>] --confirm
Skip the week: no digest goes out (refused once the send has started); owner / admin onlycommunities moderation queue <cid>
Reported posts / comments: pending plus recently resolved (staff)communities moderation resolve <cid> <reportId> --action remove|dismiss [--note] --confirm
Remove the content (author notified) or dismiss the report
Conversations (client DMs)
The creator ↔ client direct-message inbox. Conversations are keyed by client id (wcid).
conversations list [--unread] [--limit] [--cursor]
Inbox, newest activity firstconversations get <clientId>
One conversation (inbox row)conversations messages <clientId> [--limit] [--cursor]
Messages, newest first (deleted show as tombstones)conversations send <clientId> (--content <t> | --content-file <path>) [--message-id <id>]
Send a staff message;--message-idis an idempotency keyconversations edit <clientId> <messageId> (--content | --content-file)
Edit an own messageconversations delete <clientId> <messageId> --confirm
Soft-delete an own message (tombstone for both sides)conversations react <clientId> <messageId> --emoji <emoji>
Toggle your emoji reactionconversations read <clientId>
Mark as read (shared team read marker)conversations broadcast (--recipients <id…> | --recipients-file <path>) (--content | --content-file) [--notify-by-email] --confirm
DMs every listed client (max 5000), irreversibly; the fan-out runs async
Clients
clients list [--search <q>] [--with-products | --without-products] [--created-from <date>] [--created-to <date>] [--limit] [--cursor]
Browse clients, newest first.--created-from/--created-tofilter by the date the client was added (YYYY-MM-DDor an ISO 8601 date-time; a plain--created-today keeps the whole day)clients add --email <e> [--first-name] [--last-name] [--phone] [--language]
Create a contact only: product access is granted separatelyclients get <id>
One clientclients tags/clients tags list
The workspace tag registry: the idsclients import --tag, segments and campaign link actions refer toclients tags create --name <n> [--color <c>]
Create a tag (names are unique per workspace; a duplicate answers 409)clients tags update <tagId> [--name <n>] [--color <c> | --clear-color]
Rename / recolour: the id stays, so every assignment, segment and link action keeps pointing at itclients tags delete <tagId> --confirm
Permanent: the tag is removed from every client that carries itclients tags assign --id <a,b,…> [--add <tagId,…>] [--remove <tagId,…>]
Add / remove tags on many clients at once (tag ids; batches of 200)clients email-log <id> [--limit <n>]
Emails Zanfia sent to the client and their delivery statusclients bundled-access <productId>
Clients whose access to the product comes from a PARENT product's plan (bundle) rather than a direct grant; withclients list --productthis is the product's full access censusclients accesses <id> [--status active|revoked|expired]
Every product-access grant the client holds, newest firstclients notes <id>
Internal CRM notes (team-only, never shown to the customer)clients add-note <id> --text <t>
Add an internal note (up to 5000 characters)clients update <id> [--first-name] [--last-name] [--phone] [--language]
Edit the profileclients grant <productId> --email <e> [--first-name] [--last-name] [--price <priceId>] [--plan <planId>] [--until <date>] [--welcome-email]
Grant product access without a purchase (free enrollment under an existing price / plan, optionally time-limited; silent unless--welcome-email)clients revoke <productId> --email <e> [--run-automations] [--cancel-subscription] [--silent]
Revoke access: never refunds; a subscription Stripe already ended is revoked, a live one is refused unless--cancel-subscriptioncancels it in the same step;--silentskips e-mails and automationsclients change-period <productId> --email <e> (--end <date> | --add-days <n>)
Adjust a time-limited access window (absolute end or relative days; negative shortens)clients cancel-subscription <productId> --email <e> --confirm
Stop a client's subscription billing: access runs to the end of the paid period; a subscription Stripe already ended is reported as already ended and the access is brought in line with Stripe (409 = not a subscription / gateway not connected)clients change-email <id> --email <e>
Change the email (profile, product access and login move with it)clients import <csvFile> [--tag <tagId> …]
Bulk-import contact-only clients from a CSV (email+ optionalfirstName/lastName/phoneNumber/language); batches of 100, existing emails dedupedclients merge-preview --survivor <wcid> --duplicate <wcid>
Dry-run a merge: shows what would happen and prints thepreviewHashthe merge requiresclients merge --survivor <wcid> --duplicate <wcid> --preview-hash <hash>
Irreversibly merge the duplicate into the survivor (grants move/dedupe, community content re-points, duplicate tombstoned); a stale hash is refused (409); live subscriptions block itclients bulk-revoke <productId> (--email a,b,… | --all) [--run-tasks] --confirm
Revoke many clients' access in one background run (no refunds): 202 with the Trigger run id; progress in the dashboard Tasks tabclients bulk-change-period <productId> (--email a,b,… | --all) (--end-date <iso> | --add-days <n>) --confirm
Shift many clients' access end at once (absolute date or relative days): 202 with the run idclients bulk-delete <productId> (--email a,b,… | --all) --confirm
Remove many clients' grants on a product in one run (records stay; useclients deletefor those): 202 with the run idclients bulk-revoke-filtered --product <id,id> --filter=<json|file> [--run-tasks] --confirm
Revoke every client matching a Customers-list filter (productsFilter,search,tagIds,productIds,emailSubscriptionStatus,minTotalSpent,ordersFilter, …) in up to 10 products: resolved server-side, 202 with the run idclients delete --id <a,b,…> --confirm
Delete (tombstone) clients by id: ids with active product access are skipped (revoke first); batched by 200clients export [--out <file>] [--columns tags,stats,emailSubscription,activity,accesses,invoiceData,attribution,identifiers] [--created-from <iso>] [--created-to <iso>] [--limit <n>] [--search] [--with-products|--without-products]
Clients CSV written to--out(defaultzanfia-clients.csv): identity columns always, extra groups via--columns; reports the row count and whether the cap truncated itclients devices list <id>
The client's active device slots under the customer access policy (site update access-policy): id, kind, name, first/last seen, active untilclients devices revoke <id> <deviceId> --confirm
Sign one device out; it must claim a slot againclients devices reset <id> --confirm
Sign every device out and clear the client's slots: reportsrevokedCountclients email-subscription --id <a,b,…> --status subscribed|unsubscribed [--reason imported-from-other-list|existing-customers-consent|asked-directly|other] [--reason-note <t>] --confirm
Subscribe / unsubscribe many clients to marketing e-mail. Subscribing records the consent basis you attest to (--reasonis required then); batches of 200clients sms-subscription --id <a,b,…> --status subscribed|unsubscribed [--reason explicit-sms-consent|asked-directly|other] [--reason-note <t>] --confirm
The same for marketing SMS (a separate consent: an e-mail opt-in never covers SMS)clients emails inbox [--limit <n>]
One-off client e-mails across every customer: drafts and sentclients emails list <clientId>
One customer's e-mails, newest firstclients emails draft <clientId> --subject <s> (--content <md> | --content-file <path>) [--from-name] [--reply-to] [--from-email] [--branded | --plain]
Create a draft personal e-mail to one customer. Nothing is sentclients emails update <clientId> <emailId> [same flags]
Edit a draft (a sent e-mail is a record)clients emails delete <clientId> <emailId> --confirm
Delete a draftclients emails send <clientId> <emailId> --confirm
⚠️ Real send: the e-mail goes to the customer and cannot be recalled. A failed hand-off leaves the draft re-sendableclients emails delete-drafts (--ids <a,b,...> | --subject <subject> | --all) [--preview] [--confirm]
Delete drafts (and failed hand-offs) across customers: the listed ids (fromclients emails inbox), every draft with exactly this subject, or all; sent e-mails are never touched.--previewonly counts; a delete needs--confirmand never removes more than it countedclients automation-runs list <clientId>
One customer's workflow runs in progress (as on the customer card), across every workflow, newest first; test runs excludedclients automation-runs stop <clientId> --confirm
Take the customer out of every workflow run in progress: each run ends cancelled and no further step runs (a step already sending finishes); they can enter again when a trigger fires later. One run only:workflows runs stop
A 503 from the bulk commands means the Trigger.dev scheduler is not configured on that environment.
Campaigns (email broadcasts)
Drafts are harmless; campaigns send emails real people. Audience is either a saved segment (--segment, re-resolved at send time) or every subscribed client (--all-subscribed, the default).
campaigns list
Email campaignscampaigns get <id>
One campaign: status, sender, audience, stats (delivery funnel + engagement: unique opens/clicks with rates over the tracked base, attributed orders), contentcampaigns create --name <n> [--subject] [--preheader] [--content <md> | --content-file <path>] [--from-name <n>] [--reply-to <e>] [--from-email <e>] [--segment <segmentId> | --all-subscribed] [--background-color <hex>] [--content-background-color <hex>] [--link-color <hex>]
Create a draft. Nothing is emailed; only--nameis required now (subject/content/sender are enforced bysend);--from-emailneeds the workspace's verified sending domaincampaigns update <id> [same flags]
Edit a draft (campaigns past draft are immutable except cancel);--contentreplaces the whole bodycampaigns delete <id>
Permanent: draft/cancelled/failed only (sent campaigns are audit records and stay)campaigns test <id> [--to <email>]
Send a test of the saved campaign to up to 3 addresses (--torepeatable or comma-separated; default: your own address). Subject prefixed[Test], sample personalization values; never the audiencecampaigns send <id> [--at <iso>] --confirm
Sends real bulk email to the whole resolved audience, irreversibly; omit--atto send now,--atschedules (max 30 days out)campaigns cancel <id>
Cancel a scheduled or in-flight campaign: stops the remaining sends onlycampaigns reschedule <id> --at <iso>
Move a scheduled campaign to a new send time (min 2 minutes, max 30 days out); it stays scheduled and goes out only at the new time. Drafts:campaigns send --atcampaigns recipients <id> [--status pending|sent|delivered|skipped-suppressed|skipped-muted|failed|bounced|complained|clicked] [--link <linkId>] [--limit] [--cursor]
Recipient drill-down of the report with per-recipient opens/clicks (-= not measured);--linklists who clicked one link (id fromcampaigns links)campaigns links <id>
Link table of a sent campaign: per-link unique/total clicks, most-clicked first
SMS campaigns
One-off text broadcasts sent through the workspace's own Twilio / SMSAPI account (connect it in the dashboard first). Drafts are harmless; sms-campaigns send texts real people, is billed by your provider per message part and cannot be undone. The audience is every SMS-subscribed client (--all-subscribed, the default) or a saved segment; a segment is always narrowed to clients with a phone number and an explicit SMS consent.
sms-campaigns list
Campaigns with status, audience, recipients, sent count and billed partssms-campaigns get <id>
One campaign: provider, sender, audience, stats, opt-out line, bodysms-campaigns create --name <n> [--body <t> | --body-file <path>] [--opt-out-hint <t>] [--provider twilio|smsapi] [--sender <name>] [--segment <segmentId> | --all-subscribed]
Create a draft. Nothing is sent. The body supports{{firstName}}/{{lastName}}sms-campaigns update <id> [same flags] [--clear-opt-out-hint] [--default-provider] [--default-sender]
Drafts only: anything past draft is immutable except cancelsms-campaigns delete <id> --confirm
Permanent; draft / cancelled / failed only (sent campaigns are audit records)sms-campaigns send <id> [--at <iso>] --confirm
⚠️ Real send. Omit--atto send now;--atschedules (max 30 days out)sms-campaigns cancel <id>
Stop a scheduled or in-flight campaign: only the remaining sendssms-campaigns recipients <id> [--status pending|sent|failed|skipped-invalid-number] [--limit] [--cursor]
Per-recipient drill-down with billed parts and provider details
A 503 from sms-campaigns send means the Trigger.dev scheduler is not configured on that environment.
Segments
Saved, ANDed condition sets over the clients list: the audiences campaigns target. Condition flags are repeatable; --json-file takes the conditions as a JSON array instead. --owns / --not-owns count effective access (a direct grant or access through a bundle); use --owns-direct <pid> / --not-owns-direct <pid> to match only a direct grant on that product (bought or granted by hand); in JSON, "accessScope": "direct" on the product condition.
segments list
Audience segmentssegments create --name <n> [--has-tag <tagId>] [--without-tag <tagId>] [--owns <pid>] [--not-owns <pid>] [--spent-gte <minor>] [--spent-lte <minor>] [--subscription pending|subscribed|unsubscribed] | --json-file <path>
New segment (name unique per workspace, max 80 chars)segments update <id> [--name] [condition flags | --json-file <path>]
Rename and/or replace the condition set wholesalesegments delete <id>
Delete: sent campaigns are unaffected; drafts targeting it fail to send until re-pointedsegments preview [condition flags | --json-file <path>]
Count the matching clients without saving anything
Signup forms
Standalone signup forms (newsletter / lead capture). forms list also shows legacy product-bound forms, which stay editable only via the product editor.
forms list
Signup forms (standalone + legacy product-bound, flagged)forms get <id>
One form: settings + full embed configforms create --name <n> [--tags <id,id>] [--opt-in double|single] [--redirect <url>] [--header] [--description] [--button-text] [--success-message] [--phone off|optional|required] [--sms-consent on|off] [--sms-consent-text <t>] [--variant basic|card|richCard|minimal|split|gradient]
New standalone form (double opt-in by default)forms update <id> [--name] [--tags <ids>] [--opt-in double|single] [--redirect <url|none>] [--confirmation-subject] [--confirmation-intro] [--clear-confirmation-email] [--header] [--description] [--button-text] [--success-message] [--variant] [--json <patch>]
Edit: flat config flags are merged over the current embed config for you; a--jsonconfigobject is sent as the full replacement (flat flags still win);noneclearsforms delete <id> --confirm
Permanent; legacy product-bound forms are refused by the APIforms submissions <id> [--limit <n>]
Submissions, newest firstforms submit <formId> --email <e> [--first-name] [--phone] [--language] [--tags <id,id>] [--field key=value] [--page-url <url>] [--ref-code <c>] [--marketing-consent|--no-marketing-consent] [--consent-statement] [--json <body>] --confirm
Sign a person up through a form as your own server does: consent asclients add, a double opt-in form e-mails a confirmation, fields overwrite, the form's automations startforms snippet <id> [--lang pl|en] [--first-name] [--redirect <url>] [--platform-url <origin>]
HTML of the native form for ANY website (form markup + the platform script). See Your form on your own site
--phone adds a phone number field to the form; --sms-consent on adds the marketing-SMS consent checkbox (it needs the phone field; --phone off switches it off too). The full per-form copy of the double opt-in confirmation e-mail is set through forms update --json with a confirmationEmailOverride object (null restores the workspace copy); --confirmation-subject / --confirmation-intro remain as the short legacy form.
Testimonials (wall of love)
Moderation, manual adds, collection forms, and asking customers for a testimonial. Statuses: pending, approved, hidden.
testimonials list [--status pending|approved|hidden] [--product <pid>] [--tag <t>] [--limit] [--cursor]
Management view: pending/hidden includedtestimonials add --quote <t> --author <n> [--type text|imported] [--rating 1-5] [--role] [--avatar-url] [--products <id,id>] [--tags <a,b>] [--imported-from <source>] [--client <clientId>]
Add a text testimonial or record an imported one (video adds are not supported)testimonials update <id> [--approve | --hide | --pending] [--feature | --unfeature] [--quote] [--rating <1-5|none>] [--author] [--role <r|none>] [--avatar-url <url|none>] [--products <ids>] [--tags <tags>] [--client <id|none>]
Moderation sugar plus field patches;noneclears nullable fields, empty string clears liststestimonials delete <id> --confirm
Permanenttestimonials request --client <clientId> [--product <pid>] [--form <formId>] --confirm
Sends a real email asking the customer for a testimonial: the form is resolved server-side (--form, else the product's active form, else the workspace-general one)testimonials forms list
Collection forms (the public submission pages)testimonials forms create --name <n> [--slug] [--product <pid>] [--headline] [--questions "Q1|Q2"] [--collect-rating on|off] [--collect-role on|off] [--allow-video on|off] [--thank-you <msg>]
New collection form (omit--product= workspace-general)testimonials forms update <id> [--name] [--slug] [--product <pid|none>] [--headline <t|none>] [--questions "Q1|Q2"] [--collect-rating on|off] [--collect-role on|off] [--allow-video on|off] [--thank-you <msg|none>] [--activate | --deactivate]
Edit / activate / deactivate a collection formtestimonials forms delete <id> --confirm
Permanent
Brand profiles
Extracted or hand-built brand identity (colors, fonts, voice) used by content generation and site update theme --from-brand.
brand list
Brand profilesbrand get <id>
One profile: also how you poll a pending extractionbrand create --name <n>
Empty profile to fill in withbrand updatebrand extract <url> [--name <n>] [--into <brandProfileId>]
Extract a profile from a website (async: startspending; pollbrand getuntilready/failed);--intore-extracts into an existing profile, preserving manually edited fieldsbrand update <id> [--name <n>] [--json-file <path>]
Rename, or patch{ name?, visual?, editorial? }: the given top-level blocks are replaced wholesalebrand delete <id>
Permanent: no undo, no archive; content generation that referenced it falls back to no brand context
Team
Workspace team members and invitations. Roles: admin, editor, content_creator, viewer.
team members
Team membersteam invitations
Pending invitations (accepted/revoked/expired ones are history)team invite <email> --role admin|editor|content_creator|viewer [--name <n>]
Invite someone: they receive the accept link by emailteam resend <invitationId>
Re-email a pending invitation with a fresh link (60 s spacing)team revoke <invitationId>
Revoke a pending invitation: the link stops working immediatelyteam set-role <userId> <role>
Change a member's workspace roleteam remove <userId>
Remove a member: access lost immediately; cannot be undone (re-invite to restore)
Orders
orders list [--product] [--status paid|pending|refunded|failed] [--from/--to] [--search] [--include-test] [--limit/--cursor]
Order history, newest first: manual wire-transfer orders awaiting your decision are--status pending; simulated test purchases only with--include-testorders get <orderId>
One order: payment, refund, order bumps, invoiceorders approve <orderId> [--issue-invoice] --confirm
Approve a manual (wire-transfer) order: grants access and sends the purchase email;--issue-invoicealso issues an invoice via the connected invoicing account (409 = already decided)orders reject <orderId> --confirm
Reject a manual order: cancels it and notifies the buyer (409 = already decided)orders mark-test <orderId> [--undo] --confirm
Mark a real order as a test: it leaves revenue, charts and reports, the buyer keeps access;--undoputs it back (409 = already in that state)orders mark-refunded <orderId> [--revoke-access] [--note <text>] --confirm
Record that you returned the money outside Zanfia (bank transfer, cash): nothing is charged or paid out and no correcting invoice is issued (do that in your invoicing tool); the order counts as refunded.--revoke-accessalso ends the buyer's access (refused while a live subscription would keep billing);--notegoes on the order's audit trail (max 500 chars)orders export --from <date> --to <date> [--product] [--status] [--search] [--include-test] [--max-rows <n>] [--out <file>]
Orders CSV to a file: the--from/--towindow is required; same filters asorders list(row cap default 10000, max 20000)
Subscriptions
Recurring-billing follow-ups: failed charges, customers on manual-renewal plans, and subscriptions that are ending.
subscriptions failed
Failed subscription / installment charges: customer, product, amount, attempts and Stripe's next automatic retry, invoice id, hosted invoice URL; notes when Stripe is not connected (live dunning fields unavailable)subscriptions retry <invoiceId> --confirm
Retry a failed charge by its Stripe invoice id (in_…): a real charge attempt; result ispaid,requires_action(share the hosted invoice URL),card_declined,not_eligibleorfailedsubscriptions upcoming
Customers on manual-renewal plans whose access ends soon: days left, last reminder, and the renewal link they need to clicksubscriptions ending
Live subscriptions set to cancel at period end: still paying until the date shown, then they churn unless the customer reverses the cancellation
Instalment arrears (subscriptions arrears)
Buyers whose instalment plan stopped being paid before its last instalment (checked live in Stripe; a buyer who paid outside Stripe is not listed), and the workspace setting for such a non-payment: revoke ends the access like a plain subscription end, keep leaves it and lists the buyer. The default is revoke; the setting applies to plans bought since it shipped, older plans always keep their access, and a plan paid in full is never affected. subscriptions arrears keep on one buyer („Zostaw”) only saves a mark (who, when): the access, Stripe and the buyer are not touched.
| Command | Purpose |
|---|---|
subscriptions arrears setting | What happens when a buyer stops paying instalments |
subscriptions arrears set-setting --action <revoke|keep> --confirm | Change what happens when a buyer stops paying instalments |
subscriptions arrears list [--product-id <value>] [--include-kept|--no-include-kept] | List buyers behind on instalments („Zaległości”) |
subscriptions arrears keep <productId> <clientEmail> --confirm | Keep a behind-on-instalments buyer's access („Zostaw”) |
Stats
Range via --period 24h|7d|30d|90d or --from/--to (ISO date or instant); --currency forces the report currency.
stats overview
High-level revenue / order overviewstats sales [--period] [--from] [--to] [--currency]
Sales report: revenue, refunds, AOV, per-product breakdownstats recurring [--currency]
MRR / ARR snapshot of the active recurring subscriptionsstats mrr-history [--period] [--from] [--to] [--currency]
MRR over time + churn series from the daily snapshotsstats funnel [--period] [--from] [--to]
Checkout funnel: sessions → conversions, per checkoutstats traffic [--period] [--from] [--to]
Page traffic: views / entries / purchases per page and sourcestats sessions [--period] [--from] [--to] [--limit]
Recent checkout sessions across every checkoutstats product <id> [--currency]
Per-product pulse (last 30 days): revenue, students, engagement, attentionstats conversion-events [--order] [--checkout] [--session] [--status a,b] [--client a,b] [--limit]
Conversion-tracking event log: did the Purchase reach Meta / GA4 / TikTok, and with what resultstats conversion-health [--since-days <n>]
Per-integration delivery health verdict (healthy / degraded / failing / inactive)
Discounts
discounts list
Discount codesdiscounts create --code <CODE> (--percent-off <pct> | --amount-off <minor> --currency <c>) [--product <id> …] [--max-redemptions <n>] [--starts <iso>] [--ends <iso>] [--forever] [--per-unit] [--inactive]
New code: exactly one of--percent-off/--amount-off;--foreverapplies to every subscription renewal (default: first payment only);--per-unittakes--amount-offoff each unit bought (default: once per order)discounts update <discountId> [--percent-off | --amount-off --currency] [--product <id> …] [--max-redemptions <n> | --clear-max-redemptions] [--ends <iso> | --clear-ends] [--forever] [--amount-scope order|unit] [--active | --inactive]
Change value, products, limits, dates, active state;--amount-scopeswitches a fixed amount between once per order and per unitdiscounts archive <discountId>
Soft delete: the code stops working at checkoutdiscounts delete <discountId>
Permanent: preferdiscounts archive
Referrals
Referral and affiliate programs: a program targets one or more products, enrolled clients get a personal ref code, and purchases attributed to a code count as referrals. Milestone rewards (referral kind) are approved automatically or by you; rewards whose delivery failed can be retried. Program ids come from referrals list, participant and ledger-entry ids from referrals get. The deep config (milestones, commission, attribution) travels as one JSON object: --json <file|inline>.
referrals list
Programs with kind, status, enrollment mode, product count and headline stats (participants, valid referrals, rewards pending approval)referrals get <programId>
Program dashboard: summary, participants (ref codes, stats), rewards pending approval and failed deliveries:--jsondumps the full documentsreferrals create --kind referral|affiliate --name <n> --product <id,id,…> [--enrollment open|inviteOnly|closed] [--status active|inactive] [--json <json|file>]
Create a program:--jsoncarriesmilestones(reward ladder),commission(affiliate terms) andattribution(window, coupon attribution, two-sided discount); flat flags win over--jsonkeysreferrals update <programId> [--name <n>] [--status active|inactive] [--product <id,id,…>] [--enrollment <mode>] [--json <json|file>]
Update a program:--productand--jsonmilestonesare full replaces (read the program first)referrals archive <programId> --confirm
Archive a program: new referrals stop counting and it leaves the active listreferrals enroll <programId> (--client <clientId> | --email <email>) [--first-name <n>] [--last-name <n>]
Enroll one client as a participant and print their ref code: idempotent, a repeat reportsalreadyEnrolledreferrals enroll-all <programId> --confirm
Enroll every client with access to one of the program products: reportsenrolled/alreadyEnrolled/skippedWithoutAccount(legacy access without a login account)referrals resolve <programId> <entryId> --decision approve|reject --confirm
Approve (delivers the reward) or reject a reward waiting for manual approval; the reply says whether delivery already happenedreferrals retry-delivery <programId> <entryId>
Re-run delivery for a reward listed under failed deliveries
Workspace
Workspace-level settings. The timezone drives workspace-scoped scheduling such as workflow "wait until" steps. The video quality profile depends on the plan and stays in the dashboard.
workspace/workspace get
Name, timezone, auto-transcription and the default transcription languageworkspace update [--name <n>] [--timezone <iana> | --inherit-timezone] [--auto-transcribe on|off] [--transcription-language pl|en|default]
Change settings.--inherit-timezoneclears the override so the workspace follows the owner's account timezone
Workflows
The CLI inspects workflow automations, pauses/resumes them, (re)starts runs, and can author them: create a draft, replace the draft graph with the same JSON the dashboard builder saves, publish, duplicate and delete. Publishing makes a workflow live (real emails, SMS and webhooks fire on real customer events), so it requires --confirm. The visual builder in the dashboard stays the comfortable way to design a graph; workflows draft set is for scripts and agents. Runs execute for real (emails, tags and webhooks fire), so the run commands require --confirm.
workflows list
Automations with status, triggers, product scope, node counts, last runworkflows get <id> [--graph]
One workflow in detail.--graphwalks the steps in execution order (trigger → actions → delays → condition branches), with each email's subject and a short plain-text excerpt of its body (library emails show their name and id). Shows the published graph and, when it differs, the draft below it; with-o jsonthe raw response carriesdraftGraph,publishedGraphandhasUnpublishedChangesworkflows runs <id> [--limit]
Recent runs, newest firstworkflows runs stop <id> <runId> --confirm
Take the person out of one run: it ends cancelled and nothing after the current step runs (a step already sending finishes); a finished run is left as it is, and they can enter again when the trigger fires laterworkflows pause <id>
Stop reacting to trigger events and cancel runs in progress (contacts waiting in a delay get no further steps)workflows resume <id>
Restart a paused workflow (needs a published version)workflows retry <id> <runId> --confirm
Re-run a finished run with its original trigger context as a new run (202 →runId)workflows test-run <id> --context=<json|file> [--trigger-node <nodeId>] --confirm
Start a run from a hand-written trigger context (clientEmail,clientFirstName,productId,orderId, …): not a dry run; a 503 means the Trigger.dev scheduler is not configured on that environmentworkflows create --name <n> [--description <t>] [--product <id,id>] [--trigger <eventType>] [--kind transactional|marketing]
Create a draft. Nothing runs. Without--triggerthe canvas starts empty.--kind marketing= unsubscribe link in every e-mail and e-mail/SMS steps skipped for unsubscribed contacts; omitted = derived from the triggers at publishworkflows draft get <id> [--published] [--out <file>]
Print the draft graph as JSON (or the live one with--published); edit it and feed it back toworkflows draft setworkflows draft set <id> --json <file|json> [--name] [--description] [--product <id,id>] [--kind]
Replace the draft graph ({ nodes, edges }); the published version keeps running untouched. A 422 lists every validation issue with its node id and reason codeworkflows publish <id> --confirm
⚠️ Make the draft live. The graph must validate (422 otherwise)workflows duplicate <id> [--name <n>]
Copy a workflow: the copy is an unpublished draftworkflows delete <id> --confirm
Permanent;workflows pauseis the reversible alternative
Funnels
Marketing funnels are named step chains that reference existing checkouts, pages and workflows: a funnel never owns the artifacts its steps point to. Creation is recipe-first; from-template also generates the page steps with AI.
funnels list
Funnels with status, recipe, step countfunnels get <id>
One funnel's full step chain, workflows and products; a webinar funnel adds its event (start, join link, every reminder with its send moment and state)funnels create --recipe <lead_magnet|tripwire|low_ticket|sell_product|webinar> --product <id> --lang en|pl [--name]
Draft funnel over the product's existing checkouts plus the recipe email sequence as a draft workflow (409 when the product lacks the free/paid checkout the recipe needs).webinartakes any product and creates three empty page steps; the output names the next command (funnels generate-page <id> --step 1 --event-start …)funnels update <id> [--name] [--workflows a,b] [--json <patch>]
Rename, swap workflows, or re-wire the step chain (steps/workflowIdsare full replaces)funnels status <id> --set draft|live|archived
Status transitions (all reversible)funnels delete <id> --confirm
Hard-delete the funnel document only; prefer archivingfunnels generate-page <id> --step <n|stepId> --lang en|pl [--brief] [--template] [--event-start <iso>] [--event-timezone] [--event-duration <min>] [--event-mode live|on_demand] [--join-url <url>]
AI-generate the page for one page-type step and wire it (queued, 1-5 minutes; consumes AI credits). A webinar registration step builds the whole family around the--event-*values;--join-urlis the ClickMeeting / Zoom link the pages and reminders carryfunnels templates
The template catalog behindfrom-template(id, recipe, product requirement, step chain;available: no= coming soon)funnels from-template <templateId> --product <id> --lang en|pl [--name] [--event-start <iso>] [--event-timezone] [--event-duration <min>] [--event-mode live|on_demand] [--join-url <url>] [--live-room <roomId>] [--page-template <id>] --confirm
Create the funnel from a template and queue the AI generation of its page steps (202 → per-steprunIdtable + skipped steps; consumes AI credits);--page-templateseeds every page with one design template (e.g.course-fresh, thank-you steps take its companion).--join-urlgives a live webinar its ClickMeeting / Zoom link (watch page button, calendar, thank-you page, reminders).--live-roomruns the webinar template on a webinar that plays a recording, without--product(needs thelive_roomsplan feature). Pollfunnels get <id>until the steps have pagesfunnels from-pages --page <id,id,…> [--name] [--json <file>]
A funnel record over existing hosted pages in visitor order (first = entry); nothing is generatedfunnels stats <funnelId>
Per-step numbers (views / entries / signups, checkout sessions / completed, OTO viewed / accepted) plus totals;n/a= not applicable to that step kind
Blog
The workspace blog (Your site → Blog): Markdown posts served at /blog/{slug} on your store host, with an RSS feed at /blog/feed. Drafts are visible to your team only; publish makes a post public. Walkthrough and the assistant's part: Blog with the assistant.
| Command | Purpose |
|---|---|
blog list | List blog posts |
blog create --title <value> [--slug <value>] [--content <value>] [--excerpt <value>] [--cover <value>] [--tags <a,b,...>] [--category-ids <a,b,...>] [--seo <json|file>] [--publish|--no-publish] [--published-at <value>] | Create a blog post |
blog get <id> | Blog post detail |
blog update <id> [--title <value>] [--slug <value>] [--content <value>] [--excerpt <value>] [--cover <value>] [--tags <a,b,...>] [--category-ids <a,b,...>] [--seo <json|file>] [--published-at <value>] | Update a blog post |
blog publish <id> [--publish|--no-publish] | Publish or unpublish a blog post |
blog delete <id> --confirm | Delete a blog post |
blog schedule <id> --at <value> | Schedule a blog post |
blog categories list | List blog categories |
blog categories create <name> [--slug <value>] [--description <value>] [--seo <json|file>] | Create a blog category |
blog categories update <id> [--name <value>] [--slug <value>] [--description <value>] [--seo <json|file>] | Update a blog category |
blog categories delete <id> --confirm | Delete a blog category |
Site layout and menu
What frames the blog (Your site → Look and menu, „Wygląd bloga”) and the site's one shared menu, under zanfia site. site layout pages lists the pages that can frame the blog with each one's issue; site layout set page --page <id> gives the blog that page's header, menu and footer, site layout set storefront brings back the storefront shell. site menu set --menu takes the WHOLE menu as a JSON array (inline or a .json file; read site menu get --json first). Every save is live at once and checked by the same rules as the dashboard.
| Command | Purpose |
|---|---|
site layout get | Get the site layout |
site layout pages | List pages that can frame the blog |
site layout set <kind> [--page <value>] | Set what frames the blog |
site menu get | Get the shared site menu |
site menu set --menu <json|file> | Set the shared site menu |
Site redirects
The site's 301 redirect map (Your site → Redirects): old addresses of a site moved to Zanfia and where they go now. A line is picked by its from; every change is checked by the same rules as the dashboard (no chains, loops or duplicates, at most 500 lines).
| Command | Purpose |
|---|---|
site-redirects list | List site redirects |
site-redirects create <from> <to> | Add a site redirect |
site-redirects update <from> [--new-from <value>] [--to <value>] | Change a site redirect |
site-redirects delete <from> --confirm | Remove a site redirect |
Pop-ups
Pop-ups on your site (Your site → Pop-ups): a signup form that opens on entry or after scrolling, after the visitor's cookie decision, as a bottom sheet on phones, and never again after a signup. create makes a draft (a new signup form unless you pass content.formId); status <id> active puts it on the site within a few minutes. Needs the pop-ups feature on your plan.
| Command | Purpose |
|---|---|
popups list | List pop-ups |
popups create --name <value> [--priority <n>] [--language <pl|en|default>] [--copy <json|file>] [--content <json|file>] [--design <json|file>] [--trigger <json|file>] [--targeting <json|file>] [--frequency <json|file>] [--schedule <json|file>] | Create a pop-up |
popups get <id> | Pop-up detail |
popups update <id> [--name <value>] [--priority <n>] [--language <pl|en|default>] [--copy <json|file>] [--content <json|file>] [--design <json|file>] [--trigger <json|file>] [--targeting <json|file>] [--frequency <json|file>] [--schedule <json|file>] | Update a pop-up |
popups status <id> <status> | Activate or pause a pop-up |
popups delete <id> --confirm | Delete a pop-up |
popups stats <id> [--from <value>] [--to <value>] | Pop-up stats |
Pages
Custom hosted HTML pages, served on your storefront domain. Statuses: draft, published.
pages list
Hosted pagespages get <id>
One page, HTML body includedpages create --slug <s> --title <t> (--file <path> | --html <h>) [--publish [--allow-placeholders]]
New page, draft unless--publish;--allow-placeholderspublishes even with fields still to fill inpages update <id> [--slug] [--title] [--file | --html] [--no-cookie-banner | --cookie-banner] [--event-start <iso>] [--event-timezone] [--event-duration <min>] [--join-url <url>] [--replay-url <url>] [--live-room <roomId>]
Edit;--no-cookie-bannerhides the workspace cookie banner on this page only (pixels still wait for consent given elsewhere). The--event-*/ link options set a webinar page's date and links: a new--event-startmoves the reminders,--join-urlchanges the watch page button at once,noneclears a link or detaches the webinar (--live-roomneeds thelive_roomsplan feature)pages publish <id> [--only-this-page] [--dry-run] [--allow-placeholders]
Go live. A site's home page takes every site page along, drafts too, unless--only-this-page;--dry-runlists what would change;--allow-placeholderspublishes even with fields still to fill inpages unpublish <id> [--only-this-page] [--dry-run]
Back to draft (a site's home page: the whole site, unless--only-this-page)pages delete <id>
Delete the page and its HTMLpages set-main <id>
Use a published page as the storefront homepagepages unset-main
Revert the storefront to the product gridpages generate --intent <lead-magnet|webinar|sales-page|thank-you|waitlist|newsletter|challenge|application|upsell|low-ticket|generic> --title <t> --brief <text|file> [--template] [--product] [--checkout] [--form] [--source-url --source-mode rebuild|inspired] [--slug] [--delivery-url] [--delivery-name] [--event-start <iso>] [--event-timezone] [--event-duration <min>] [--event-mode live|on_demand] [--replay-media <mediaId>] [--opt-in-mode single|double] [--form-tags a,b] [--json <file>] --confirm
AI-generate a draft page (consumes AI credits; 202 →runId, pollpages list). Signup intents also provision a form, a thank-you page and a draft delivery automation; a 503 means the Trigger.dev scheduler is not configured on that environmentpages edit <pageId> --instruction <text|file> --confirm
AI-edit the page's content or design from a described change: synchronous, up to 1-2 minutes; prints the change summary and the revision id of the previous HTMLpages templates
Design templates usable aspages generate --templateseeds (id, name, category, intents)pages revisions <pageId>
HTML snapshots taken before each overwrite (newest first)pages restore <pageId> <revisionId> --confirm
Roll back to a revision (the current HTML is snapshotted first, so a restore is itself undoable)pages family-step <pageId> --step <follow-ups|thank-you-page|room-page>
Build one missing piece of a page family (the editor checklist's rows):follow-ups= the reminder ladder of a dated webinar page (e.g. your own page that got its date withpages update --event-start), as drafts thatpages activate-automationsschedules; thank-you / watch pages are AI generations (credits)pages activate-automations <pageId> --confirm
Publish the page's draft delivery automation and schedule its webinar follow-up broadcasts: emails send for real; placeholder copy is skipped
Social media
Posts to the connected social accounts (Marketing → Social). Statuses: draft, scheduled, published, partially_failed (some accounts got the post, at least one failed), failed. Scopes: social:read / social:write.
social accounts
Connected accounts and their target keys (linkedin,facebook:{pageId}, …)social posts [--status <s>] [--limit <n>]
Browse drafts, scheduled posts and publish historysocial get <id>
One post: content, media, schedule, publish results (permalinks)social draft --text <t> --targets <a,b> [--media <ids>] [--first-comment <t>]
Save a draft (appears in the dashboard Posts view)social schedule [id] --at <iso> [--text … --targets … [--media] [--first-comment]]
Schedule an existing draft or new content (>= 1 min ahead)social publish [id] [--text … --targets linkedin [--media] [--first-comment]]
Publish now: LinkedIn only (text + images)social update <id> [--text] [--targets] [--media | --clear-media] [--first-comment | --clear-first-comment]
Edit a draft or scheduled post (a scheduled one stays at its time); published posts can't be editedsocial unschedule <id>
Move a scheduled post back to drafts: it is not published at its timesocial delete <id> --confirm
Delete a post; a published post stays live on the network, only Zanfia's record goes
Media library
media upload --file <path> [--name] [--mime] [--folder] [--description]
Upload a file; prints the media id and public URLmedia upload-video --file <path> [--title] [--mime] [--wait] [--poll-timeout <s>]
Upload a video for lessons (Bunny); transcoding runs after uploadmedia list [--folder <name>]
Browsemedia get <id>
One item; showsuploadStatusmedia rename <mediaId> [--name] [--description | --clear-description]
Rename and/or edit the descriptionmedia move <mediaId...> (--folder <name> | --unfile)
File items into a folder, or unfile themmedia transcript <mediaId> [--words] [--out <file>]
A video transcript (plain text;--wordsadds word-level timings and implies JSON)media generate-image --prompt <text> --confirm
Generate an AI image into the media library (spends AI credits)media transcribe <mediaId> --confirm
Start a video transcription (spends transcription minutes)media delete <id> [--force]
Delete (--forceif still referenced elsewhere)media folder list
Foldersmedia folder create <name>
New folder (idempotent by name)media folder rename <name> --to <newName>
Rename: renaming onto an existing folder merges into itmedia folder delete <name>
Delete a folder: its items are unfiled, never deleted
Videos need transcoding
media upload-video without --wait returns while the video is still processing. Poll
media get <id> until uploadStatus is ready, then bind it with
courses lesson set-video.
Was this article helpful?

