@extole/cli
v0.7.12
Published
Extole developer CLI
Readme
@extole/cli
Developer CLI for the Extole API.
Table of Contents
- Requirements
- Install
- Who Is This For
- Quickstart
- Getting a Token
- Auth
- Ping / Whoami
- Person
- Share Links
- Rewards
- Reward Suppliers
- Programs
- Campaigns
- Audiences
- Components
- Zones
- Events
- Webhooks
- Notifications
- Reports
- Health
- Chat
- Feedback
- API
- Output Conventions
- Config File
- Contributing
- License
Requirements
- An Extole account with an API token (see Getting a Token)
- Node.js 22.12 or higher (only required for the from-source install method)
Install
Binary (no Node required) — Mac and Linux:
curl -fsSL https://raw.githubusercontent.com/extole/extole-cli/master/install.sh | shInstalls to /usr/local/bin/extole when that directory is writable. Otherwise (the common case on macOS, where /usr/local/bin is root-owned) it installs to ~/.local/bin/extole and adds that directory to your shell profile's PATH. Open a new terminal afterward, or run the export PATH=... line the installer prints. Override the location with EXTOLE_INSTALL=/your/path.
Binary — Windows:
Download extole-windows-x64.exe from the latest release, rename it to extole.exe, and place it somewhere on your PATH.
Homebrew (Mac):
brew install extole/tap/extolenpm (requires Node 22.12+):
npm install -g @extole/cliFrom source (requires Node 22.12+):
git clone https://github.com/extole/extole-cli.git
cd extole-cli
npm install
npm linkVerify: extole --version
Who Is This For
This CLI targets developers and technical operators working with the Extole API — engineers integrating Extole into their platform, technical support staff diagnosing reward and event issues, and solutions engineers configuring programs.
All CLI commands require a token with CLIENT_ADMIN scope. If you hit a 403, your token may lack the necessary scope — see Getting a Token.
Quickstart
Install using any method from the Install section above, then:
# Authenticate
extole auth login --token YOUR_TOKEN
# Verify it works
extole whoami
# Try something useful
extole programs
extole rewards --email [email protected]
extole chat "what campaigns are live on this account?"For help on any command: extole --help, extole <command> --help
Getting a Token
Log in to my.extole.com, navigate to Settings → API Access, and create or copy an API token. The token needs CLIENT_ADMIN scope.
Contact your Extole account team if you do not have access to API settings.
Auth
extole auth login --token TOKEN # save token (account name derived from client)
extole auth login --token TOKEN --account acme --set-default # save token, set as default
extole auth login --token TOKEN --account staging # save additional account
extole auth default acme # change default account
extole auth list # show all accounts with default marker
extole auth status # verify token + connectivity
extole auth token # print raw token for the default account
extole auth token --account acme # print raw token for a named account
extole auth logout --account acme # remove accountauth token prints the raw token to stdout (credential warning on stderr):
extole auth token | pbcopy
curl -H "Authorization: Bearer $(extole auth token)" https://api.extole.io/v6/webhooks/builtAll commands use the default account unless --account NAME is specified.
export EXTOLE_TOKEN=your-token-here # use this token for all commands
export EXTOLE_ACCOUNT=acme # use this account for all commandsPing / Whoami
extole ping # verify connectivity (exit 0 = OK)
extole whoami # verify token and show client identity, scopes, and expiry
extole whoami --account other-client # check a non-default account
extole whoami --jsonwhoami calls /v4/tokens and returns client name, scopes, token type, and days until expiry.
Person
extole person get --email [email protected] # profile data
extole person steps --email [email protected] # step history (default 25)
extole person steps --email [email protected] --limit 100
extole person steps --email [email protected] --listen # tail live steps (Ctrl+C to stop)
extole person steps --email [email protected] --duration 30 # tail and auto-exit after 30s
extole person steps --email [email protected] --listen --json
extole person rewards --email [email protected] # rewards for this person
extole person rewards --email [email protected] --status EARNED
extole person rewards --email [email protected] --json
extole person relationships --email [email protected] # advocate↔friend referral relationships
extole person relationships --email [email protected] --json
extole person stats --email [email protected] # personal + referral network stats
extole person stats --email [email protected] --json
extole person report --id <person_id> # profile events report ALL_TIME (~30-90s)
extole person report --email [email protected] # looks up person ID then runs reportrelationships shows each referral link — role (ADVOCATE/FRIEND), program, other person's ID, channel, and creation date. stats shows two rows: the person's own AOV/LTV/conversions and the same metrics aggregated across their referral network.
Share Links
Two directions:
extole share-links list --email [email protected] # all share links for a person
extole share-links list --email [email protected] --label credit-cards
extole share-links list --email [email protected] --json
extole share-links lookup <share-code> # reverse: code → owner
extole share-links lookup https://refer.example.com/<share-code>
extole share-links lookup <share-code> --jsonlist returns share links (label, code, URL); use --label to filter across programs. lookup is the reverse: given a share code or full URL, returns the owning person and program.
Rewards
extole rewards --email [email protected]
extole rewards --email [email protected] --status EARNED
extole rewards --email [email protected] --limit 50
extole rewards get <reward_id> # full detail including coupon code
extole rewards get <reward_id> --steps # also show recipient step history
extole rewards get <reward_id> --json
extole rewards expire <reward_id> # mark a reward as expired (asks for confirmation)
extole rewards expire <reward_id> --message "manual cleanup" # optional reason, stored for audit purposes
extole rewards expire <reward_id> --yes # skip confirmation prompt
extole rewards history <reward_id> # state-transition timeline (EARNED → SENT → FULFILLED → ...)
extole rewards state-summary # account-wide reward counts by state, bucketed over time
extole rewards find-coupon <code> # reverse lookup: who got this coupon and was it used?Reward states: EARNED, FULFILLED, SENT, REDEEMED, CANCELED, FAILED, REVOKED, EXPIRED
Error messages are unambiguous about whether the person exists:
No person found for [email protected]— email not in ExtoleNo rewards found for [email protected] (person ID: abc123)— person exists, genuinely zero rewards
rewards history is the go-to for "why is this reward stuck?" — each row shows the state change, when it happened, and whether the transition succeeded. rewards state-summary gives ops-level aggregate counts plus a per-week breakdown. rewards find-coupon reverse-looks up a coupon code to find who got it, what state it's in, and whether Extole was told it was redeemed.
REDEEMEDmeans "someone told Extole it was used" (via a redemption event or webhook) — not "definitely used at point of sale." Without that integration, a coupon can be used at checkout and the reward stays inSENTforever.
Reward Suppliers
Inspect and manage reward suppliers (BHN, coupons, custom, Tango) — used by reward rules in campaigns to mint the actual reward value.
Inspect:
extole reward-suppliers
extole reward-suppliers --filter manual
extole reward-suppliers get <supplier-id>
extole reward-suppliers coupons <supplier-id>
extole reward-suppliers coupons <supplier-id> --listCreate a MANUAL_COUPON supplier:
extole reward-suppliers create --type MANUAL_COUPON --name "Test Coupons" \
--face-value 25 --face-value-type USD --warn-limit 10 --dry-run
extole reward-suppliers create --type MANUAL_COUPON --name "Test Coupons" \
--face-value 25 --face-value-type USD --warn-limit 10Create a CUSTOM_REWARD supplier:
extole reward-suppliers create --type CUSTOM_REWARD --name "Statement Credit" \
--face-value 50 --face-value-type USD --custom-reward-type ACCOUNT_CREDITCreate a TANGO_V2, SALESFORCE_COUPON, or other supplier (full JSON):
extole reward-suppliers create --body \
'{"reward_supplier_type":"TANGO_V2","name":"Gift Card","face_value_type":"USD","face_value":25,"account_id":"...","utid":"..."}'Upload coupon codes:
extole reward-suppliers upload-coupons <supplier-id> --codes CODE1,CODE2,CODE3
extole reward-suppliers upload-coupons <supplier-id> --file ./coupons.txt
extole reward-suppliers upload-coupons <supplier-id> --file ./coupons.txt --dry-runThe list uses the /built endpoint so component-bundle suppliers display their resolved values. coupons refuses non-MANUAL_COUPON suppliers — other types mint codes on demand. create supports typed flags for MANUAL_COUPON and CUSTOM_REWARD; use --body <json> for TANGO_V2 and SALESFORCE_COUPON. upload-coupons accepts a flat text file (one code per line) or --codes for inline lists; use --dry-run to preview.
Programs
extole programs # list LIVE programs and campaigns
extole programs --all # include PAUSED, STOPPED, NOT_LAUNCHED
extole programs --jsonCampaigns
Inspect per-campaign configuration: which quality rules are turned on, and what the MaxMind fraud-scoring controller-trigger settings are.
extole campaigns quality-rules <campaign-id> # enabled quality rules only
extole campaigns quality-rules <campaign-id> --include-disabled # also show disabled rules
extole campaigns quality-rules <campaign-id> --json # raw QualityRuleResponse[]
extole campaigns maxmind <campaign-id> # enabled MaxMind triggers only
extole campaigns maxmind <campaign-id> --include-disabled # also show disabled triggers
extole campaigns maxmind <campaign-id> --json # raw trigger array
extole campaigns reward-rules <campaign-id> # per-role reward rules: rewardee, trigger, supplier, constraints
extole campaigns reward-rules <campaign-id> --json # raw RewardRuleResponse[]
extole campaigns publish <campaign-id> # validate, build, and publish
extole campaigns publish <campaign-id> --launch # publish and take live immediately
extole campaigns publish <campaign-id> --message "wired new reward webhook"Quality Rules
quality-rules calls GET /v2/campaigns/{id}/incentive/quality-rules and renders the configured legacy quality rules. Each row shows the rule type, whether it is enabled, which action types it applies to (ANY_CLICK, ANY_SHARE, ANY_REGISTER, ANY_PURCHASE, ANY_PROMOTION), and any rule-specific properties (e.g. cap_number=10, lookback_interval=7 on REFERRAL_CAP).
MaxMind
maxmind walks the built campaign (GET /v2/campaigns/{id}/built) and surfaces every trigger_type: MAXMIND controller-trigger, with its step, phase, risk_threshold, ip_threshold, allow_high_risk_email, and default_quality_score. When a trigger has thresholds different from the recommended value of 20 (the legacy default was 5), an advisory is printed to stderr. The advisory does not appear in --json output.
Publish
publish calls POST /v2/campaigns/{id}/publish — the same endpoint components deploy --publish uses under the hood — so that taking a config change live doesn't require dropping to extole api. Add --launch to also set the start date to now in the same call, and --message to attach a changelog note to the publish.
Audiences
Inspect audiences, their size, members, and recent push/sync history. Useful when verifying that an async audience operation (SFDC sync, file import, replace, etc.) completed without round-tripping through the my.extole UI.
extole audiences list # audiences on the account (default --limit 100)
extole audiences list --filter sfdc # match name substring
extole audiences list --limit 500 # raise cap on big accounts
extole audiences get <name|id> # name, size, recent history summary
extole audiences members <name|id> # person_id + email rows
extole audiences members <name|id> --email-only # emails only, one per line
extole audiences members <name|id> --limit 500 --offset 0 # paginate
extole audiences history <name|id> # recent ADD / REMOVE / REPLACE / ACTION runs
extole audiences history <name|id> --listen # tail new runs as they arrive (Ctrl-C to stop)
extole audiences history <name|id> --listen --interval 3 # custom poll interval (default 5s)The <audience> argument resolves by exact ID, then exact name, then case-insensitive substring. Multiple matches prompt for a more specific input.
Components
Extole's configuration is built from components — typed, composable building blocks that define programs, rules, rewards, emails, integrations, and more. Understanding the component model is prerequisite to building or modifying offer programs programmatically.
The type system is nominal and open-ended — types form a hierarchy (reward-supplier-v10.0 is a subtype of reward-supplier), and components wire together via named sockets. The reliable way to learn what a type requires is to find a known-good instance and read its config; extole chat can also answer type-specific questions.
extole components # all components, account-wide
extole components --program <id> # scoped to one program
extole components --filter-type reward-supplier # filter by type (matches subtypes too)
extole components --filter "gift card" # filter by name substring
extole components get <component-id> # full config + variables
extole components get <component-id> --tree # full real structure, from the campaign's true root
extole components get <component-id> --tree --basic # ownership subtree only, skips the extra resolution calls
extole components get <component-id> --sockets # socket references to other components
extole components get <component-id> --built # resolved buildtime values, not raw expressions
extole components types # all concrete types in this account
extole components types --parent rule # subtypes of a given parent type
extole components types --parent rule --tree # rendered as a hierarchy--filter-type does substring matching against the full type hierarchy (--filter-type reward matches reward-v10.0, reward-rule-v10.0, etc.). --tree on get renders the full downstream subgraph like tree/npm ls — box-drawing connectors, each node's type, and the socket it's installed into (e.g. [rewardRules]), which is often the fastest way to understand how a campaign is actually wired together. It walks up to the campaign's true root first, regardless of which component id in the tree you passed, so it always shows the complete structure — the id you asked for is marked ← requested so you don't lose track of it. It also resolves and attaches webhooks, event streams, and cross-campaign socket subscriptions — these are modeled as independent resources that point at a component rather than being owned children, so a plain ownership walk would otherwise miss them; add --basic to skip that extra resolution on accounts with a lot of webhooks/event-streams where the added latency isn't worth it. --built calls GET /v1/components/{id}/built so that variables backed by javascript@buildtime:... expressions (e.g. a webhook ID discovered by tag) show their resolved value instead of the raw expression — the same pattern webhooks get --built uses.
Creating Integration Components
components create attaches a component to a campaign. Primary use case: webhook integrations where the component discovers its webhook(s) by tag at publish time.
# Minimal — component with no webhook wiring
extole components create --name my_integration --campaign <id>
# With webhook discovery — component finds the webhook by tag when campaign is published
extole components create \
--name iterable_integration \
--display-name "Iterable" \
--campaign <campaign-id> \
--description "Sends referral events to Iterable" \
--webhook-tag "iterable-events"
# Multiple webhooks (auto-named from tag)
extole components create \
--name my_integration \
--campaign <campaign-id> \
--webhook-tag "my-integration-events" \
--webhook-tag "my-integration-subscriptions"
# Explicit variable name (varName:tag)
extole components create \
--name my_integration \
--campaign <campaign-id> \
--webhook-tag "eventsWebhookId:my-integration-events"
# Print payload without creating (useful for verifying buildtime expressions)
extole components create --name my_integration --campaign <id> --webhook-tag my-events --dry-runEach --webhook-tag generates a javascript@buildtime variable that resolves the webhook ID at publish time; the resolved ID is stored (no runtime tag lookup).
Deploying Integration Bundles
components deploy bundles a local directory and uploads it. Use for full integration components (integration-v10.0, extension, etc.) where component, sub-components, webhooks, and scripts live together as a directory tree.
# Deploy a new bundle (creates its own campaign)
extole components deploy --source ./my_integration
# Deploy and publish immediately
extole components deploy --source ./my_integration --publish
# Update an existing component in place
extole components deploy --source ./my_integration --component <component-id>
# Update and publish
extole components deploy --source ./my_integration --component <component-id> --publish
# Print resolved component.json contents (post-%{...}% include expansion) without uploading
extole components deploy --source ./my_integration --dry-run
# Show full API error details on failure
extole components deploy --source ./my_integration --verboseRunning deploy without --component always creates a new campaign. Pass --component with the ID from the first deploy to update in place. Settings values can reference external files using %{/path/to/file.js}% — the CLI inlines the file content before uploading.
Patching Component Settings
Update one or more settings on an already-deployed component without redeploying the bundle. Settings are integration-specific — the names and valid values depend on the component type (e.g. a webhook integration might have webhookUrl and authHeader; a CRM connector might have instanceUrl and objectType).
extole components set <component-id> --setting webhookUrl=https://example.com/hook
extole components set <component-id> --setting webhookUrl=https://example.com/hook --setting objectType=Lead
extole components set <component-id> --setting webhookUrl=https://example.com/hook --dry-runValues are coerced to the setting's declared type before sending — check a setting's type via components get <component-id> (each entry in variables has a type). BOOLEAN accepts true/false; INTEGER accepts a whole number; list/object-typed settings (STRING_LIST, STRING_MAP, JSON, etc.) require a valid JSON value, e.g. --setting tags='["a","b"]'. Anything else is sent as a plain string, unchanged. A value that doesn't match its type's contract errors immediately, before any network call — no silent guessing. Structural settings (MULTI_SOCKET, SOCKET, etc.) aren't settable this way at all; those are wired via components create/duplicate/deploy. Changes on LIVE campaigns are staged until you republish (via components deploy --publish or my.extole).
For multi-line/script-shaped settings, use --setting-file <name>=<path> instead of typing the value inline — it reads the new value from a file, shows a diff against the current value, and asks for confirmation before sending (skip the prompt with -y/--yes for non-interactive/agent use):
extole components set <component-id> --setting-file requestScript=request.js
extole components set <component-id> --setting-file requestScript=request.js --yesMixing --setting and --setting-file in one call sends both in the same PUT, but only the --setting-file entries get a diff — the confirmation prompt lists any inline --setting values alongside it so it's clear they're included too.
Duplicating Components
Duplicate an existing component instead of authoring one from scratch. This one endpoint does two different things depending on --target-campaign:
extole components duplicate <component-id> --target-campaign <campaign-id> # copy just this component into an existing campaign
extole components duplicate <component-id> --target-campaign <campaign-id> --target-socket <name> --target-component <component-id> # ...into a specific socket on a specific component
extole components duplicate <component-id> --display-name "My Integration Copy" # no --target-campaign: duplicates the ENTIRE owning campaign as a new one
extole components duplicate <component-id> --dry-runPassing --target-campaign duplicates just the one component and installs the copy into that existing campaign — the pattern for adding a copy of a field, rule, or action alongside what's already there. Omitting --target-campaign duplicates the source component's entire owning campaign as a brand-new campaign (NOT_LAUNCHED), not just the one component — this is the pattern for installing a reusable library integration as a fresh campaign. The CLI shows which of the two will happen and asks for confirmation before acting (skip with -y/--yes).
--target-socket requires --target-component, the component in the target campaign that owns the socket — the platform's underlying lookup needs an absolute path to that component, not just the campaign, so the CLI resolves --target-component's id to that path automatically.
Installing a Component into a Socket
components socket add/remove wraps a separate, purpose-built socket-installation endpoint — an alternative to components duplicate --target-socket above that only needs the component that owns the socket, not the target campaign.
extole components socket add <target-component-id> --setting rewardSuppliers --source <template-component-id>
extole components socket add <target-component-id> --setting rewardSuppliers --source <template-component-id> --display-name "Gift Card"
extole components socket remove <target-component-id> --setting rewardSuppliers --component <installed-component-id>add duplicates --source and installs the duplicate into --setting on <target-component-id> — it doesn't reference or modify the source, so the same template can be installed into multiple integrations independently. remove deletes the installed duplicate outright, not just unlinks it — any local customization made on it after installing is lost. Use components get <id> --tree afterward to confirm the change; full-tree resolution shows socket-installed components in place.
Finding What References a Component
components references <id> answers "what would break if I delete or rename this?" — it's the reverse lookup of --sockets. It shows every COMPONENT_REFERENCE/COMPONENT_REFERENCE_LIST setting across the account that already selects this component, or could.
extole components references <component-id>Read-only, one GET, no request body.
Deleting Components
extole components delete <component-id> # prompts for confirmation
extole components delete <component-id> --confirm # skip prompt (for scripts)
extole components delete <component-id> --dry-run # show what would be deletedDeleting a root component archives its entire campaign; the CLI warns if it's a root before prompting. For a full walkthrough of the bundle format and deployment workflow, contact your Extole solutions engineer.
Downloading a Campaign Bundle
Download the live campaign bundle from the platform and unpack it locally — useful for inspecting what's deployed or diffing against a local source tree.
extole components download <campaign-id> # unpack to ./campaign-name/
extole components download <campaign-id> --output ./my-local # specify output directoryThe download reconstructs a readable directory structure: campaign.json at the root, assets with the extra platform directory level removed, and creative ZIPs renamed from numeric IDs to their logical names (derived from the step/trigger names in campaign.json). The output is not directly deployable via components deploy — component.json is not reconstructed — but it is suitable for inspection and diffing.
Zones
extole zones
extole zones --json
extole zones core
extole zones tag <zone_name>
extole zones call <zone_name> --email <email>
extole zones call <zone_name> --email <email> --param partner_user_id=abc123
extole zones call <zone_name> --email <email> --jsonzones call POSTs to /v5/zones/<zone_name> — useful for testing FRONTEND_CONTROLLER + DISPLAY pipelines without a browser.
Events
Firing Events
extole events fire <event_name>
extole events fire <event_name> --live
extole events fire <event_name> --sandbox my-sandbox
extole events fire lead_created --email [email protected]
extole events fire lead_created --email [email protected] --live
extole events fire <event_name> --param key=value [--param key=value ...] --live
extole events fire <event_name> --data '{"email":"[email protected]","amount":"500"}'
extole events fire <event_name> --dry-run
extole events report <event_id>
extole events fire <event_name> --live --listen
extole events fire <event_name> --live --listen --listen-timeout 30
extole events fire <event_name> --email <email> --live --trace
extole events fire <event_name> --email <email> --live --trace --trace-webhook <id>
extole events fire <event_name> --email <email> --live --trace --trace-timeout 15Sandbox mode is the default. Use --live for production, --dry-run to preview. --sandbox sets the sandbox name (default: production-test).
Trace (--trace)
Use --trace after firing to see exactly which campaigns the event reached. Steps are filtered by cause_event_id matching the fired event, then grouped by campaign. If no campaign matched, --trace checks /v2/campaigns/built to determine why — distinguishing between "event isn't wired to any campaign" and "campaigns use the event but targeting filtered this person out." --trace automatically discovers webhooks attached to campaigns using this event and probes each for dispatches caused by the fired event. Use --trace-webhook <id> to override and check a specific webhook directly.
Listening to Events
extole events listen is the preferred way to tail live events. extole stream is the underlying command and accepts the same options.
extole events listen # all events (noisy on prod — add filters)
extole events listen --event-type INPUT # filter by event type (repeatable)
extole events listen --event-type INPUT --event-type REWARD
extole events listen --filter lead_created # filter by event name (repeatable)
extole events listen --email [email protected] # filter to one person
extole events listen --app-type my_integration # filter by source (repeatable)
extole events listen --sandbox container-test # filter by sandbox/container
extole events listen --duration 30 # auto-exit after 30 seconds
extole events listen --tail 10 # exit after 10 events (non-interactive tools)
extole events listen --json # newline-delimited JSONCreates an ephemeral /v6/event-streams session, polls every 2.5s, and cleans up on Ctrl+C or when --duration/--tail is reached.
Recommended starting filters for production clients (unfiltered streams are very noisy):
extole events listen --event-type INPUT # business events fired by integrations
extole events listen --event-type INPUT --event-type REWARD # business events + reward issuance
extole events listen --app-type my_integration # only events from a specific integrationEvent type reference:
| Type | What it is |
|---|---|
| INPUT | Business events fired by integrations (lead_created, opportunity_closedwon, etc.) |
| REWARD | Reward state transitions (issued, fulfilled, redeemed) |
| STEP | Processing steps triggered by input events |
| SHARE | Share link clicks and share actions |
| REFERRED / REFERRED_BY | Friend-side referral events |
| IDENTIFIED | Identity resolution events |
| REDEEMED | Redemption events |
| MESSAGE | Email/message delivery events |
| SEND_REWARD | Reward send attempts |
| INTERNAL | Internal system events |
| DATA_INTELLIGENCE | Fraud/quality scoring events |
| AUDIENCE_MEMBERSHIP_* | Audience list membership changes |
| ACTION | Legacy action events |
Webhooks
Outbound webhooks send Extole events to external systems via HTTP POST. Four types:
| Type | When it fires | Unique field |
|---|---|---|
| GENERIC | Person/consumer journey events (referral, share, purchase, custom input events) | — |
| CLIENT | Admin/operational events — config change, report complete, campaign started, webhook failure, etc. | — |
| REWARD | Reward state transitions (EARNED, FULFILLED, FAILED, etc.) | filters |
| PARTNER | Manual dispatch only — no automatic trigger | response_body_handler (parses HTTP response body) |
extole webhooks
extole webhooks --filter-type GENERIC
extole webhooks --filter "sfdc"
extole webhooks --json
extole webhooks get <webhook-id>
extole webhooks get <webhook-id> --built
extole webhooks create --name "SFDC Events" --url https://example.com/hook
extole webhooks create --name "SFDC Events" --url https://example.com/hook --type GENERIC
extole webhooks create --name "Iterable Events" \
--url https://api.iterable.com/api/events/track \
--type CLIENT --tag iterable-events --request-file request.js
extole webhooks create --name "Reward Hook" --url https://example.com/hook \
--type REWARD --filter-state EARNED --filter-state FULFILLED
extole webhooks create --name "Test" --url https://example.com/hook --dry-run
extole webhooks delete <webhook-id>Editing an Existing Webhook
Update a single field on an existing webhook via a partial PUT — e.g. tweak the request or response_body_handler script without touching anything else (name, URL, tags, etc. stay untouched). Shows a diff against the current value and asks for confirmation before sending, since this changes live dispatch/fulfillment logic. Skip the prompt with -y/--yes for non-interactive/agent use.
extole webhooks edit <webhook-id> --field request --file request.js
extole webhooks edit <webhook-id> --field request --file request.js --dry-run
extole webhooks edit <webhook-id> --field response_body_handler --file handler.js --yesUse webhooks get --field <name> to pull a field's raw value out of an existing webhook — combined with edit, this copies a proven request/response_handler script from one webhook onto another instead of writing it from scratch:
extole webhooks get <source-webhook-id> --field request > request.js
extole webhooks edit <target-webhook-id> --field request --file request.jsWebhook Types and the --tag Flag
Tags enable the component-driven integration pattern — components discover which webhook to call by tag at publish time rather than hardcoding an ID.
# Create a webhook with a discovery tag
extole webhooks create \
--name "Iterable Events" \
--url https://api.iterable.com/api/events/track \
--type GENERIC \
--tag "iterable-events"
# Create a component on the same campaign that discovers it by tag
extole components create \
--name "iterable_integration" \
--campaign <campaign-id> \
--webhook-tag "iterable-events"When the campaign is published, the component resolves the webhook ID from the tag and stores it — no separate campaign controller needed.
Attaching Webhooks to Campaigns (Controller Model)
attach wires a webhook directly to a campaign via a controller — for simpler cases where no component is needed.
extole webhooks attach \
--webhook <webhook-id> \
--campaign <campaign-id> \
--event signed_up
extole webhooks attach \
--webhook <webhook-id> \
--campaign <campaign-id> \
--event purchase \
--event-type STEP # STEP = internal processing step; INPUT = integration event (default)
# Attach multiple events to one campaign — defer publish until the last one
extole webhooks attach --webhook <id> --campaign <id> --event signed_up --skip-publish
extole webhooks attach --webhook <id> --campaign <id> --event purchase # publishes once here--quality controls dispatch priority: HIGH (normal), LOW (best-effort), ALWAYS (bypasses campaign targeting rules). Defaults to HIGH.
Live Testing
webhooks trace temporarily wires a URL to a campaign event and tails dispatch results — no external tunnel needed:
extole webhooks trace \
--url https://my-server.com/hook \
--campaign <campaign-id> \
--event signed_up \
--yes # skip confirmation promptInspecting Dispatch History
extole webhooks dispatches <webhook-id> # what Extole tried to send (attempt records)
extole webhooks dispatches <webhook-id> --limit 50
extole webhooks dispatch-results <webhook-id> # HTTP outcomes: response codes + bodies
extole webhooks dispatch-results <webhook-id> --json
extole webhooks listen <webhook-id> # tail dispatch results in real time (Ctrl-C to stop)
extole webhooks listen <webhook-id> --interval 5 # custom poll interval (default 3s)
extole webhooks listen <webhook-id> --show-body # print full response body per row
extole webhooks listen <webhook-id> --duration 60 # auto-exit after 60 secondsdispatches = one record per attempt. dispatch-results = HTTP response side (non-200s, timeouts, error bodies). listen is the live-tail version — seeds on first poll so only new attempts appear.
Notifications
Show recent platform notifications for this account — webhook failures, integration errors, and other actionable system alerts. Same data as my.extole.com/notifications. Useful when debugging "the integration looks wired up but nothing's happening" — the notifications often name the exact campaign and event the platform couldn't process.
extole notifications # last 20 most-recent-first
extole notifications --limit 50 # paginate (default 20)
extole notifications --level ERROR # ERROR / WARN / INFO filter
extole notifications --tag technical # tag filter (server-side; repeat for multiple)
extole notifications --listen # tail new ones as they arrive (default 10s poll)
extole notifications --json # raw response, suitable for scriptingEach notification shows time, level, name, message, and key data fields (campaign_id, controller_id, person_id, cause_event_id) — most of which feed into other CLI commands.
Reports
Discovery → describe → run is the self-documenting flow — each step's output feeds the next:
extole reports recommended # curated starting picks for this account (default 5)
extole reports types --filter <term> # find reports by name/description/categories
extole reports describe --type <name> # required vs optional params, types, defaults, allowed values
extole reports run --type <name> -p key=value # executedescribe is the contract for run — surfaces every parameter: required vs optional, type (TIME_RANGE, STRING, ENUM, etc.), defaults, and allowed values.
extole reports # list saved report runners
extole reports types # list ALL report types (no filter)
extole reports recommended --limit 10 # more than 5 recommendations
extole reports recommended --json # raw response for scripting
extole reports run --type REPORT_TYPE [options] # create report, returns ID immediately
--days <n> set time_range to last N days (mutually exclusive with -p time_range)
-p key=value report parameter (repeatable)
--format <fmt> output format: JSON (default), JSONL, CSV — see `reports describe`
--wait poll until complete
--download download and print result (implies --wait)
extole reports status REPORT_ID # check if a report is done
extole reports download REPORT_ID # download a completed report
extole reports download REPORT_ID --wait # wait for completion then downloadThe CLI does not validate parameters client-side — it packs -p values into the request and lets the platform reject invalid ones. --download streams to stdout; pipe through jq . to pretty-print or jq -c <filter> with --format jsonl.
Examples:
# Discover active programs
extole reports run --type summary_per_program --days 365 \
-p period=MONTH -p dimensions=PROGRAM --download
# Full program event funnel
extole reports run --type summary --days 365 \
-p period=MONTH -p dimensions=PROGRAM \
-p "flows=/business-events" -p container=all --download
# Pipe to jq
extole reports run --type summary_per_program --days 365 \
-p period=MONTH -p dimensions=PROGRAM --download \
| jq '[.[].program] | unique'
# Stream large summaries as JSONL
extole reports run --type summary --days 30 \
-p period=DAY -p dimensions=PROGRAM --format jsonl --download \
| jq -c 'select(.program == "referrals")'Health
Domain and email deliverability checks. The base command is read-only — validates email domains (SPF, DMARC, DKIM, MX, A records) and program domains (CNAME/A resolution) against Extole's validation API. Nothing is created or modified by extole health itself; the provision-dkim subcommand is the only write operation, and it requires explicit confirmation.
extole health # check all email domains + program domains
extole health --domain example.com # filter to a specific email domain
extole health --json # raw validation results
# DKIM provisioning (write operation — interactive prompt by default)
extole health provision-dkim example.com # prompts before calling
extole health provision-dkim example.com --confirm # non-interactive (for scripts/CI)provision-dkim is idempotent — first call mints DKIM keys, subsequent calls return existing records. Output is the CNAME records to add to your DNS provider; re-run extole health --domain <domain> to verify.
Exit codes: 0 = all checks pass, 1 = one or more failures, 2 = bad input or auth error. Suitable for CI preflight scripts and readiness probes.
Chat
extole chat gives access to an Extole AI agent with deep knowledge of the API, component model, event semantics, and reward flows. Use it before exploring blindly.
Good uses:
- API discovery: "what endpoint do I use to filter steps by a specific event?"
- Concept clarification: "what's the difference between a journey and a step?"
- Debugging guidance: "why would a purchase event not trigger a reward?"
- Design validation: "if I want to enroll a person into a program, what's the right API approach?"
- Schema lookup: "what fields does the reward-supplier component type require?"
extole chat "what endpoint filters person steps by cause event id?"
extole chat "why aren't events firing for [email protected]"
extole chat "explain the reward supplier types available"
extole chat "what's the difference between causeEventIds and rootEventIds on steps?"Uses your stored Extole token; no separate login.
Feedback
extole feedback the --filter-state flag should mention it is REWARD-only in the help text
extole feedback auth login flow was confusing at first, needed to read the READMECreates a Jira ticket via the Extole AI agent. Includes your account name and CLI version automatically.
API
Search
Search across all published Extole API endpoints by keyword — useful when you know what you want to do but aren't sure which path or method to use:
extole api search batch # find all batch job endpoints
extole api search person --spec integration # search integration spec only
extole api search erasure --detail # show full description + request fields
extole api search reward --spec integration # reward state-transition endpointsSearches path, summary, description, and tags across the management and integration-server specs. Results update automatically as Extole publishes spec changes — no CLI update required.
Direct API Access
For endpoints not yet covered by a specific subcommand:
extole api /v2/campaigns/123/controllers
extole api /v6/webhooks/built
extole api /v2/campaigns/123/publish --method POST --body '{}'
extole api /v1/components --query limit=500 --query offset=500
extole api /v4/tokens --auth-baseGET by default. --method to override, --body for POST/PUT/PATCH, --auth-base for the auth API (api.extole.com). --query key=value (repeatable) URL-encodes and appends query parameters, so a query value with characters a shell would otherwise mangle doesn't have to be hand-embedded in the path string. Output is JSON-formatted and supports --compact.
Output Conventions
- Human-readable by default;
--jsonon all commands --compactstrips nulls and empty fields (useful for piping to agents)--verboselogs each HTTP request (→ METHOD URL) to stderr- Exit 0 = success, 1 = API error, 2 = bad input/config, 130 = Ctrl+C, 143 = SIGTERM
- Data goes to stdout, status/progress goes to stderr (pipeable)
Config File
~/.extole/config:
{
"_default": "acme",
"acme": { "token": "..." },
"acme-sandbox": { "token": "..." }
}Contributing
Bug reports and feature requests are welcome via GitHub Issues. For bugs, include the CLI version (extole --version), the command you ran, and the output.
License
MIT
