@pickfu/cli) lets you create surveys, check results, and manage surveys directly from your terminal. It supports structured JSON output for automation and CI/CD workflows.
Installation
Supported platforms
The curl/PowerShell installers download a standalone binary — no Node.js required. The
npx method requires Node.js 20+ but needs no installation.
Authentication
OAuth (recommended)
Log in through the browser to connect the CLI to your PickFu account:--profile <name> to log in to a specific saved profile — useful when switching between staging and production. The CLI picks the staging or production WorkOS environment from the selected profile’s apiHost. See Profiles.
For headless environments (servers, CI), use the --headless flag to print the authorization URL instead of opening a browser:
API key
For non-interactive environments like CI pipelines or automation scripts, use an API key instead of OAuth:Avoid
echo "sk_..." | pickfu auth set-key — the key appears in shell history. Use the interactive prompt or pipe from clipboard instead.--token flag > PICKFU_API_KEY env var > stored OAuth token > stored API key. When more than one profile is saved, --profile <name> selects which stored credential and which API host are used — see Profiles.
Managing credentials
auth status, auth logout, and auth set-key accept --profile <name> to act on a specific saved profile — for example pickfu auth status --profile staging or pickfu auth logout --profile staging. See Profiles below.
Profiles
A single CLI install can keep separate credentials and API hosts under named profiles. Use profiles to switch between environments (for example staging and production) or between different PickFu accounts without logging in and out each time. Profiles live in~/.config/pickfu/config.json with file locking and 0600 permissions. Each profile stores its own OAuth tokens or API key alongside the apiHost those credentials belong to. The first profile you save automatically becomes the default; you can change the default later with pickfu profile use.
The first time a CLI on this release reads an older flat config, it upgrades the file in place to the new { profiles, default } layout under the profile name default. No action is needed.
Save a profile
PICKFU_API_KEY, an active OAuth session, or the credentials of the current default profile) and stores it under the new name. Profile names must match ^[a-zA-Z0-9._-]{1,64}$. API hosts must be https:// URLs, or http:// on localhost, 127.0.0.1, or ::1.
profile save will not copy a stored OAuth session onto a different apiHost — those tokens are only valid for the host they were issued against. To seed a profile for a different environment (for example a sandbox host), export that environment’s PICKFU_API_KEY before saving, or run pickfu auth login --profile <name> to log in against the new host first.Switch the default profile
--profile flag and no PICKFU_API_KEY env var) use this profile’s credentials and API host.
List and inspect profiles
sk_**** with only the first and last few characters, access tokens display the first and last few characters with **** in between when long enough, and refresh tokens are reported by length only. Neither command ever prints a full credential.
Delete a profile
pickfu profile use <other> to pick a new one. The CLI refuses to delete the last remaining profile unless you pass --force-with-no-fallback, so you don’t accidentally end up with no stored credentials.
Select a profile per command
Every API command accepts a global--profile <name> flag — auth logout, auth set-key, auth status, whoami, api, and every survey, media, project, playbook, targeting, reporting, and tag subcommand. The flag chooses both the API host and the credential the request uses:
--profile, commands fall back to the default profile.
Token precedence with profiles
For a single request, the CLI picks a credential in this order:--tokenflagPICKFU_API_KEYenv var- Stored credential on the selected profile — OAuth token first, then API key
--profile selects which stored credential (step 3) is considered. It also selects that profile’s apiHost, unless PICKFU_API_HOST is set. --token and PICKFU_API_KEY override credentials only; they do not change the selected host.
PICKFU_API_HOST and stored credentials
PICKFU_API_HOST is intended for local development and staging tests. Setting it while a stored profile credential would otherwise be used is rejected:
PICKFU_API_HOST only with --token or PICKFU_API_KEY, so you never accidentally send production credentials to an arbitrary host. To point a profile at a different host permanently, save a new profile with --api-host.
Agent introspection
pickfu schema includes an agent_context block so agent consumers can discover which profiles are available without reading the config file directly:
skill_path is the absolute path of the SKILL.md usage guide that ships with the installed CLI.
Timeouts and retries
Every CLI request has a deadline and gives up cleanly if the API takes too long to respond — so a stalled upstream will never hang your terminal or your CI job indefinitely.Request timeout
Each request aborts after 120 seconds by default. Override it with thePICKFU_TIMEOUT_MS environment variable (in milliseconds):
Request timed out after Ns error and a reminder to raise PICKFU_TIMEOUT_MS if needed.
Automatic retries
The CLI automatically retries transient failures (network errors and 5xx responses) up to 3 times, but only for idempotent methods —GET, HEAD, PUT, and DELETE. POST requests are never auto-retried on network or 5xx errors, because a failed request may still have been processed server-side and retrying it could create duplicate surveys, duplicate publish charges, or duplicate tags.
If a command like pickfu survey create, pickfu survey publish, or pickfu tag create fails with a network error or 5xx, the CLI surfaces the error immediately. Check whether the operation actually succeeded (for example with pickfu survey list or pickfu tag list) before retrying by hand.
429 Too Many Requests responses are retried for every method — a 429 means the server rejected the request before processing it, so repeating is safe. The CLI honors the Retry-After header.
Delivering output
The four output-producing commands accept a--deliver flag that routes the result somewhere other than stdout:
pickfu media generatepickfu media uploadpickfu media showpickfu survey responses
--deliver when you want the CLI itself to hand the artifact to the next step of your pipeline — for example, download a generated image straight to disk in a build script, or POST a survey’s raw responses to your analytics service the moment they finish collecting. Every other command ignores the flag and rejects it if passed explicitly.
Delivery modes
--deliver takes one of three forms:
Per-command semantics for
file::
File delivery
File writes are atomic: the CLI writes to a sibling.tmp.* file and renames into place, so a reader either sees the previous file or the fully-written new one, never a half-written payload. Delivered files are created with mode 0600 — private to you — so delivery never broadens permissions on sensitive output like survey responses.
Pass --no-clobber to refuse to overwrite an existing file. The check is race-free (the CLI publishes via link()), so two concurrent writers cannot both succeed against the same path.
Webhook delivery
--deliver=webhook:<url> performs a single-shot, unsigned POST of the exact JSON payload that would have gone to stdout, with:
Content-Type: application/jsonUser-Agent: pickfu-cli/<version>
- HTTPS URLs are recommended. Plain
http:warns; other schemes are refused. - URLs with embedded credentials (
https://user:pass@host/...) are refused. - Redirects are not followed — a
3xxis reported as a delivery failure (WEBHOOK_FAILED), not silently retried as a body-lessGET. - Any non-2xx response exits nonzero. There is no automatic retry.
- The request honors
PICKFU_TIMEOUT_MS.
Stdout with --deliver
When --deliver is set to file: or webhook:, the CLI replaces the stdout payload with a delivery confirmation object. Explicit --deliver=stdout leaves the normal payload on stdout.
Introspecting delivery support
pickfu schema reports deliveryModes and deliveryDefault for each deliverable command, so integrations can discover which modes apply without hard-coding the list:
Validation errors
When the API rejects input with a422 that includes violations, for example from template limits, the CLI prints the server’s message followed by each violation’s param, allowed values, and received value:
--json, the error object adds a violations array next to status, message, code, and suggestions:
param using its allowed value. allowed can be a list of values, a { "min", "max" } range, or a targeting rule. A 422 without violations still prints Validation failed with any field errors.
Commands
All commands support--json for machine-readable output.
Surveys
Create a survey
--option-image-url instead of --option:
Correlate surveys with your own system
Add an optionalexternalId to the --from-file JSON to link a survey to a record in your own system, such as an experiment ID, a ticket number, or a database key. PickFu stores the value as-is and returns it on survey get and survey list output. It is omitted when unset.
externalId query parameter:
- 1-255 characters after trimming whitespace. Control characters are rejected.
- Matching is exact, case-sensitive, and byte-exact. It is not a text search, so send the identical string every time.
- The key is scoped to your account and doesn’t have to be unique. Several surveys can share one
externalId, for example every survey in one experiment. - Set-once. You can also claim an
externalIdon an existing draft withpickfu survey update <survey-id> --from-file. Once set it cannot be changed, and it cannot be added after the survey is published. Either attempt returns409 Conflict. Sending the same value again succeeds (idempotent). pickfu survey clonedoes not copyexternalIdto the new draft.- Mini-polls cannot have an
externalId(rejected with400).
Create a survey from a template
Templates are ready-made single-question surveys with a fixed price per respondent. To start a draft from one, put atemplate block in the --from-file JSON in place of questions. Send exactly one of questions or template.
Find a template id (cmb_<slug>) and read its blanks and option slots first:
survey.json
template block accepts id (required), prompt, blanks, context, and options:
blanksfills each{{name}}in the template’s question. Every blank is required.promptreplaces the question on templates without blanks. It’s rejected on templates that have blanks.- The template sets the question type. Where a template offers both, two options or fewer run head-to-head and three or more are ranked.
- When you omit
sampleSize,country,name, ortargeting, the draft uses the template’s defaults. - You can’t send
reporting. A template’s reporting traits are fixed. - The block is create-only. Edit the draft afterwards with
pickfu survey updateandquestions.
422 that lists every violation (see Validation errors). Run pickfu schema survey create for the full input schema. See templates in the MCP server and the Create survey API reference for details.
Get survey details
List surveys
Update a draft survey
The
--from-file JSON can also include an externalId to claim a correlation key on a draft that doesn’t have one yet. See Correlate surveys with your own system for the set-once rules.
Publish a survey
Publish a draft survey to start collecting responses:Calling
publish on an already-published survey returns a 400 Already published with a pointer to the /v1/surveys/{id}/respondents endpoint. Use pickfu survey add-respondents to extend the sample size of a running survey instead.Publishing spends the account’s credit balance first. When credits don’t cover the full cost, the saved card on file is charged for exactly the shortfall (order total minus credits and any coupon or discount already applied) and the survey launches. No credits are purchased on the caller’s behalf.Publish returns
402 Payment Required only when the account has no saved card, the card was declined, or the card requires 3D Secure authentication (which cannot be completed over the API). Add or update a card on your PickFu billing page, then retry.Publish and wait for completion
Pass--wait to publish a survey and then block until it finishes. If the survey is already in progress, --wait attaches to it instead of publishing again. The output matches survey watch, so scripts and agents get the final results from one command:
The CLI records every
--wait run as a local job (see Jobs). If you press Ctrl-C or lose your connection, re-run the same command to resume the job. The CLI never publishes the survey twice.
If an interrupted attempt may have reached the server but the survey still shows as unpublished, the CLI stops instead of retrying. Check the survey with pickfu survey get <survey-id>, then re-run with --wait --confirm-retry to publish it.
Stop a survey
Stop a live survey from collecting responses before it reaches its target sample size:Clone a survey
Duplicate an existing survey as a new draft. Useful for re-running a survey with the same audience targeting and options, or for iterating on a previous test:
The clone does not inherit the source survey’s
externalId. Set a fresh one on the new draft if you track re-runs separately in your own system.
Extract image URLs
List the image URLs of every option in a survey — useful for downloading source images for further processing:View responses
Get individual responses with explanations and demographics:Watch a survey
Block until a survey completes — useful in scripts and CI:Get Van Westendorp price sensitivity results
Read the price sensitivity analysis for avan_westendorp question:
The output also includes a
confidence rating (high, medium, or low). With --json, the response also includes the underlying curves and histograms. Run pickfu schema survey van-westendorp for the full output shape.
Delete a survey
Delete a draft survey, or archive one that has already been published:For a draft survey this permanently deletes it. For a published survey it archives instead — the survey still appears in
pickfu survey list --status archived.Jobs
Everypickfu survey publish --wait run is recorded as a local job. Use the jobs commands to inspect and clean up that history:
~/.config/pickfu/jobs.jsonl (or $XDG_CONFIG_HOME/pickfu/jobs.jsonl). The ledger is local to your machine, and pruning never changes anything on the server. jobs prune only removes finished jobs, so jobs you can still resume are kept.
Jobs are scoped to the active profile’s API host and account. A job recorded under a different profile, API host, or credential won’t resume, won’t appear in jobs list or jobs get, and isn’t touched by jobs prune. Without any credential, jobs are scoped to your local OS account.
Targeting and reporting
List available targeting traits
permalink value as a --targeting flag in survey create (e.g. --targeting amznpr).
Targeting a non-US country also localizes the survey: PickFu translates the question, context, and options into that market’s language and returns results in both languages. See International surveys and translation.
List available reporting demographics
Tags
Media
Generate an image
Generate an image from a text prompt and get a CDN URL:Reference image hosts.
--reference-image-url and pickfu survey extract-images fetch bytes from the remote host, so the URL must be on PickFu’s allowlist: pickfu.com (including media-cdn.pickfu.com), cloudinary.com, S3 (s3.amazonaws.com and regional buckets), CloudFront (d*.cloudfront.net), Amazon product CDNs (m.media-amazon.com, ssl-images-amazon.com, images-amazon.com), and Alibaba / Aliyun CDNs (alicdn.com, aliyuncs.com). If your image is elsewhere, upload it first with pickfu media upload and use the returned CDN URL.Upload media
Upload an image or video to the PickFu CDN from a URL or local file:
YouTube, Vimeo, and Wistia URLs are returned as-is since PickFu embeds them natively.
Inspect media
Fetch metadata (content type, size, ETag) for an image or video URL without downloading the full payload:Projects
Playbooks
Browse step-by-step research guides:Schema introspection
View the full input/output schemas for any command — useful for building integrations:survey publish, the schema includes asyncCapable: true, waitFlag: "--wait", and completionStates (completed, archived, deleted).
Agent workflow manifest
The CLI ships with aSKILL.md manifest that documents canonical workflows for coding agents: authenticating, publishing a survey end-to-end, iterating on creative with media, running head-to-head comparisons, watching long-running surveys, and recovering from an interrupted publish. Each workflow lists its goal, prerequisites, exact commands, error recovery steps, and safe variants.
Use it as a discovery hook so agents can load the workflow guide from the installed CLI instead of hardcoding command sequences. Print the absolute path with:
--json to get a structured payload with the file location and the CLI version it was generated for:
agent_context.skill_path in pickfu schema output.
Generic API access
Make authenticated API requests directly:Identity
Print which credential the CLI is using, decode its JWT claims, and verify it against the API:whoami reports the credential source (--token, PICKFU_API_KEY, stored OAuth session, or stored API key), the profile name, the API host, and whether verification succeeded. For OAuth credentials, it also reports the account email, user id, granted scopes, and expiry decoded from the JWT; API-key authentication has no JWT identity claims, so those fields are null. Without --local, it calls GET /v1/tags?limit=1 to confirm the credential is accepted and captures the rate-limit headers.
Exit codes are the contract for scripts and agents: 0 when the API host shown accepted the identity, 1 when the credential is missing, rejected, expired, or the host was unreachable.
The verification request can proactively refresh a stale OAuth session and rotate the stored access token.
whoami reports the post-refresh identity, so the email, scopes, and expiry always describe the credential that was used to reach the API.Local webhook development
Develop webhook consumers locally before you point PickFu at your production endpoint.pickfu listen runs a loopback HTTP forwarder that signs test events, and pickfu trigger fires those events on demand.
listen and trigger are a local fixture harness, not a production-webhook replica. They share only the signature scheme (t=<unix>,v1=<hmac-sha256> over <t>.<body>) with the production v2 webhooks PickFu delivers. The envelope fields, the extra headers, and the event types below all differ from production, which sends a narrower catalog (survey.completed, order.paid). For the real delivery shape, see the live OpenAPI contract.
Start a listener
127.0.0.1 on port 11800 (falling through to 11801 and 11802 when busy). Use --port <n> to pin a port, or --port 0 to pick an ephemeral one. On startup it prints the signing secret (whsec_…) your consumer needs, records a session file so pickfu trigger can find it, and stays running until you press ^C (exit code 130).
Only
http:// and https:// targets are allowed, and URLs with embedded credentials (user:pass@…) are rejected — the CLI never forwards credentials inside a signed payload. Trigger request bodies are capped at 1 MiB (413 Payload Too Large above that).
Trigger a test event
pickfu trigger <event> builds a signed envelope and, by default, posts it to POST /trigger on the running pickfu listen session so your endpoint sees the local listener’s signing scheme and delivery shape. These are local fixture events; they are not the production webhook catalog. Pass --forward-to to bypass the listener and post directly (--secret is required unless a previous listen session’s secret is on disk).
The event catalog:
By default,
trigger uses a synthetic stub for the event’s data.object. Survey stubs include status, numResponses, and id; media stubs include id, url, and kind. All stubs include a _pickfu_fixture: true marker so you can tell test payloads from real deliveries. To send real data instead:
trigger exits 0 when the endpoint returns 2xx and 1 when the delivery times out, errors, or the endpoint returns a non-2xx response.
Wire format and signature verification
Every fixture delivery frompickfu listen is a POST to your --forward-to URL with Content-Type: application/json and an envelope shaped like:
Content-Type and X-PickFu-Signature):
To verify a delivery, compute
HMAC-SHA256(secret, "<t>.<raw request body>") and compare it to the v1= value in X-PickFu-Signature with a timing-safe compare. Reject the request if t is more than 300 seconds away from the current time. This matches the Stripe-style scheme, so an existing Stripe verifier works with only the header name and secret prefix swapped.
pickfu listen has no retries, replay, or event history. If your endpoint returns a non-2xx the delivery is recorded as failed and dropped. Fire the event again with pickfu trigger to retry.Fixtures
Run a fixture file — a named sequence of API calls that sets up demo data with references between steps. Useful for seeding a fresh account for a demo, reproducing an integration bug on a clean environment, or standing up a scenario in a test suite.File format
A fixture is a JSON object withversion, name, optional vars defaults, and an ordered steps array. Each step is one API request.
path, query, or body:
${var:key}reads a--varoverride, falling back to thevarsdefault in the file.${stepName:dotted.path}reads a field from an earlier step’s response, indexed by that step’sname. Thedataenvelope is unwrapped automatically, and array indices work (${survey:questions.0.id}).
Guarding money-moving steps
By defaultpickfu fixtures refuses to send requests that can spend account balance. The gated paths are:
POST /v1/surveys/:id/publishPOST /v1/surveys/:id/respondentsPOST /v1/media/generate- Any
/mini-pollsendpoint POST /v1/synthetic/feedback
--allow-charges to opt in. The check runs against the fully resolved path immediately before dispatch, so a ${var:…} or ${step:…} reference cannot smuggle a charging endpoint through. Host-relative overrides (a path that starts with //) are rejected outright.
OpenClaw
See the OpenClaw page to run the PickFu CLI inside an agentic skill, including the ready-made Claw Hub market research workflow.Supported question types
Every response includes a written explanation by default. When a respondent picks an
option, ranks, rates, or clicks, they’re also required to explain why in their own words
— that’s already part of every question type except
screen_recording (which returns a
transcription instead). Do not create a multi-question survey (--question twice) just
to add a separate “explain why” question — it wastes a question slot (priced per question)
and produces redundant data. Phrase your real question with --question, and PickFu
collects the explanation automatically. The text comes back on each response as the
explanation field. See surveys overview for more.Sample sizes
Available respondent counts: 15, 30, 50, 75, 100, 200, 300, 500Screen recording questions use a different set: 5, 10, 15, 20, 25, 30, 50, 75. Passing any other value (for example
--sample-size 100) on a screen_recording survey fails validation.Screen recording questions are U.S.-only. A new survey defaults to
US. Pass --country US when you update an existing non-US survey to add a screen_recording question. PickFu rejects a screen recording question on a non-US survey with a 400 error instead of moving the survey to the U.S.