withfeedback CLI
Command-line client for withfeedback.com: testimonials, feedback moderation, CSV imports, surveys, NPS, widgets, and quota usage — from your terminal or CI.
Install
pip install withfeedback
# or run without installing:
uvx withfeedback --help
The console command is withfeedback.
Quick start with a personal access token (PAT)
Good for CI and quick scripting:
-
Log in at your withfeedback.com instance and open Account → API Tokens (
/accounts/tokens/). -
Create a token with the scopes you need (see the table below).
-
Either export it per-shell:
export WITHFEEDBACK_TOKEN=spd_abc123... withfeedback testimonials list --team <TEAM_ID> --project <PROJECT_ID>
or persist it in the OS keychain:
withfeedback login --token spd_abc123...
API access requires a plan that includes the API/CLI/MCP feature (Pro and above).
Login via OAuth2 device flow (interactive)
withfeedback login
No flags needed against withfeedback.com — the CLI defaults to the official
public client id 7yQMsnY2Is2f5tCuwwgItoQu3fRkEX2wnzIRj0Vh (public by
design — device-flow clients carry no secret). For a self-hosted instance
pass your own client:
withfeedback login --client-id <CLIENT_ID> # or WITHFEEDBACK_CLIENT_ID
What happens:
- The CLI calls
POST /o/device-authorization/and prints a verification URL plus a short code (it also tries to open your browser). - You open the URL, enter the code, and approve the requested scopes.
- The CLI polls
POST /o/token/until you approve, then stores the access and refresh tokens in the OS keychain viakeyring(macOS Keychain, Windows Credential Locker, Secret Service/KWallet on Linux) — never in a plaintext file.
When an access token expires, the CLI transparently refreshes it. Refresh
tokens rotate on every use; the rotated token replaces the stored one.
(Stored PATs are never "refreshed" — if a PAT is rejected, the CLI tells you
to create a new one and run withfeedback login --token again.)
withfeedback logout # remove stored tokens from the keychain
Token resolution precedence for every command:
--token flag > WITHFEEDBACK_TOKEN env var > OS keychain.
Commands
withfeedback login [--client-id <ID> | --token <PAT>]
withfeedback logout
withfeedback testimonials list --team <ID> --project <ID> [--status pending|approved|rejected|spam] [--rating N] [--q text] [--json]
withfeedback testimonials approve <SUBMISSION_ID> --team <ID> [--json]
withfeedback testimonials reject <SUBMISSION_ID> --team <ID> [--json]
withfeedback testimonials create --team <ID> --project <ID> --text "..." [--rating N] [--name ..] [--email ..] [--title ..] [--company ..] [--consent] [--tag t]... [--json]
withfeedback import csv FILE --team <ID> --project <ID> --text-col COL [--rating-col COL] [--name-col COL] [--email-col COL] [--consent] [--json]
withfeedback surveys list --team <ID> --project <ID> [--json]
withfeedback surveys create --team <ID> --project <ID> --name "..." --type nps|csat|ces|pmf|custom [--questions '<JSON>'] [--json]
withfeedback surveys update <SURVEY_ID> --team <ID> --project <ID> [--name ..] [--thanks-message ..] [--status draft|live|archived] [--json]
withfeedback surveys publish-version <SURVEY_ID> --team <ID> --project <ID> --questions '<JSON>' [--json]
withfeedback nps summary --team <ID> --project <ID> [--json]
withfeedback widgets list --team <ID> --project <ID> [--json]
withfeedback widgets create --team <ID> --project <ID> --type wall|carousel|collect_feedback|collect_survey|nps_popover --name "..." [--survey <ID>] [--config '<JSON>'] [--filter '<JSON>'] [--collect-video] [--inactive] [--json]
withfeedback widgets update <WIDGET_ID> --team <ID> --project <ID> [--name ..] [--survey <ID>] [--config '<JSON>'] [--filter '<JSON>'] [--collect-video/--no-collect-video] [--active/--inactive] [--json]
withfeedback widgets delete <WIDGET_ID> --team <ID> --project <ID> [--json]
withfeedback usage --team <ID> [--json]
--config, --filter, and --questions take a JSON string. Survey questions
are edited by publishing a new version (surveys publish-version), never in
place; taking a survey live is subject to your plan's live-survey limit.
Human output is plain text tables. Pass --json for machine-readable output
(raw API responses) — errors still go to stderr.
CSV import
withfeedback import csv validates the file locally (header columns, row
count), builds the column mapping from your --*-col options, and uploads
the file to the server's multipart import endpoint. Rows are processed
asynchronously and land as pending submissions; the command prints the
batch id and a status URL to poll.
withfeedback import csv reviews.csv \
--team <TEAM_ID> --project <PROJECT_ID> \
--text-col "Testimonial" --rating-col "Stars" \
--name-col "Author" --email-col "Email" --consent
Pass --consent only when display consent was actually collected for these
rows.
Required scopes per command
| Command | Required scope |
|---|---|
testimonials list |
read:feedback (or read:testimonials) |
testimonials approve / reject |
moderate:submissions (write:* cannot moderate) |
testimonials create |
write:testimonials (or write:feedback) |
import csv |
write:feedback |
surveys list, nps summary |
read:surveys |
surveys create / update / publish-version |
write:surveys |
widgets list |
read:widgets |
widgets create / update / delete |
write:widgets |
usage |
read:feedback |
Exit codes
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | General / unknown error |
| 2 | Usage / config error (bad flags, unreadable CSV, missing column) |
| 3 | Auth error (not logged in, invalid or expired token) |
| 4 | Forbidden or payment required (missing scope/role, plan without API access, quota exceeded, billing blocked) |
| 5 | Not found (unknown team/project/object or no access) |
| 6 | Validation error or invalid state transition |
| 7 | Network error (server unreachable) |
On API errors the API's error detail is printed to stderr.
Environment variables
| Variable | Default | Purpose |
|---|---|---|
WITHFEEDBACK_API_URL |
https://withfeedback.com |
API base URL (self-hosted/staging override) |
WITHFEEDBACK_TOKEN |
— | PAT (beats the keychain, loses to --token) |
WITHFEEDBACK_CLIENT_ID |
official public client id | Client id for login (override for self-hosted) |
WITHFEEDBACK_SCOPES |
all CLI scopes | Scopes requested during device-flow login |
On the first request of a run the CLI checks GET /.well-known/speedpy.json
once and warns (never fails) when the manifest is unreachable or the API is
older than the minimum this client expects.
Development
uv run --with pytest --with httpx --with click --with keyring pytest packages/withfeedback-cli/
Metadata
Release files for withfeedback 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| withfeedback-0.2.0.tar.gz | 40.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| withfeedback-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 57.0 kB
Release files / withfeedback-0.2.0.tar.gz
| Download URL | withfeedback-0.2.0.tar.gz |
|---|---|
| Size | 40.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
da2c12f81d4b998d6597667c35464437a704a7f80bb575b5e1b8f00708706e72
|
|
BLAKE2b-256 checksum How to use checksums |
cd72737b51e2267144dadbc78891855615fd382dcdac0c8c3a21f8471b442901
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.6 {"installer":{"name":"uv","version":"0.11.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / withfeedback-0.2.0-py3-none-any.whl
| Download URL | withfeedback-0.2.0-py3-none-any.whl |
|---|---|
| Size | 16.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e3c340810c336589c94e87f9cd9edf7b383e32cd660d38dfab889064692d77a2
|
|
BLAKE2b-256 checksum How to use checksums |
98e5f000c42a1b1ad0d8ae188d7e43b14bf5517674ddd638bbc619360948f3b6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.6 {"installer":{"name":"uv","version":"0.11.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|