Skip to main content
The PickFu CLI (@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

Log in through the browser to connect the CLI to your PickFu account:
This opens a browser window for OAuth authorization. Once approved, your credentials are stored locally and refreshed automatically. Pass --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 precedence: --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

Credential-management commands such as 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

Save reads whatever credential is currently active (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

Subsequent commands (with no --profile flag and no PICKFU_API_KEY env var) use this profile’s credentials and API host.

List and inspect profiles

Both commands mask secrets: API keys display as 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

Deleting the current default profile clears the default; run 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:
Without --profile, commands fall back to the default profile.

Token precedence with profiles

For a single request, the CLI picks a credential in this order:
  1. --token flag
  2. PICKFU_API_KEY env var
  3. 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:
Combine 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 the PICKFU_TIMEOUT_MS environment variable (in milliseconds):
When a request times out, the CLI exits with a 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 generate
  • pickfu media upload
  • pickfu media show
  • pickfu survey responses
Use --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/json
  • User-Agent: pickfu-cli/<version>
Behavior details:
  • 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 3xx is reported as a delivery failure (WEBHOOK_FAILED), not silently retried as a body-less GET.
  • 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.
Breaking change for scripts that read .url from stdout. With --deliver=file: or --deliver=webhook:, the stdout JSON is a confirmation and no longer contains the result payload’s url field. If you have a pipeline like pickfu media generate --json | jq -r .url, switch to jq -r .source_url when you add delivery, or use --deliver=stdout (or omit --deliver) to keep the 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 a 422 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:
With --json, the error object adds a violations array next to status, message, code, and suggestions:
Scripts and agents can fix each 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

The CLI walks you through survey creation interactively. You can also provide options via flags:
Image surveys use --option-image-url instead of --option:
Or create from a JSON file:

Correlate surveys with your own system

Add an optional externalId 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.
Look surveys up later by exact match with the externalId query parameter:
Rules:
  • 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 externalId on an existing draft with pickfu survey update <survey-id> --from-file. Once set it cannot be changed, and it cannot be added after the survey is published. Either attempt returns 409 Conflict. Sending the same value again succeeds (idempotent).
  • pickfu survey clone does not copy externalId to the new draft.
  • Mini-polls cannot have an externalId (rejected with 400).

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 a template 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:
Then create the draft. The template id and blank name below are examples; use the values from your template’s detail response.
survey.json
The template block accepts id (required), prompt, blanks, context, and options:
  • blanks fills each {{name}} in the template’s question. Every blank is required.
  • prompt replaces 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, or targeting, 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 update and questions.
A draft outside the template’s limits is rejected with a 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

Or update from a JSON file:
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 a van_westendorp question:
The command is read-only. It prints the currency, the tested price range, and the four intersection points, each with a price and a percentage: 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

Every pickfu survey publish --wait run is recorded as a local job. Use the jobs commands to inspect and clean up that history:
The CLI stores jobs in a JSONL file at ~/.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

Returns all demographic targeting traits available for the specified country. Pass each 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

Returns all demographic breakdowns available for survey results.

Tags

Media

Generate an image

Generate an image from a text prompt and get a CDN URL:
Pass one or more reference images so the model can preserve product appearance across regenerated variants — useful for iterating on packaging, A+ content, or main-image variants:
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:
Commands that can block until completion advertise it in their schema. For survey publish, the schema includes asyncCapable: true, waitFlag: "--wait", and completionStates (completed, archived, deleted).

Agent workflow manifest

The CLI ships with a SKILL.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:
Add --json to get a structured payload with the file location and the CLI version it was generated for:
The same path is also reported as 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

The listener binds to 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 from pickfu listen is a POST to your --forward-to URL with Content-Type: application/json and an envelope shaped like:
Headers on every fixture delivery (production sends only 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 with version, name, optional vars defaults, and an ordered steps array. Each step is one API request.
References are string-substituted anywhere in a step’s path, query, or body:
  • ${var:key} reads a --var override, falling back to the vars default in the file.
  • ${stepName:dotted.path} reads a field from an earlier step’s response, indexed by that step’s name. The data envelope is unwrapped automatically, and array indices work (${survey:questions.0.id}).
Steps run sequentially. On the first failure the run stops and reports which steps completed — earlier steps are not rolled back, so you may need to delete any drafts or tags the fixture created before rerunning.

Guarding money-moving steps

By default pickfu fixtures refuses to send requests that can spend account balance. The gated paths are:
  • POST /v1/surveys/:id/publish
  • POST /v1/surveys/:id/respondents
  • POST /v1/media/generate
  • Any /mini-polls endpoint
  • POST /v1/synthetic/feedback
Pass --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, 500
Screen 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.

Need help?

Or contact support@pickfu.com for assistance.