Authentication¶
Four mechanisms are accepted, and they exist for different jobs. Pick by who is calling, not by which is easiest to paste.
| Mechanism | Use it for | Not for |
|---|---|---|
| Personal access token | your own scripts, CI, an MCP server on your machine | anything you hand to a third party |
| OAuth2 | a tool acting on somebody else's behalf | server-to-server where you own both ends |
| JWT | a first-party front end you are writing against your own account | long-lived automation |
| Session | the dashboard itself | your code |
API access is a paid feature
The API, the CLI and the MCP server require a plan that includes them (Pro
and above). Requests from lower plans return 403. This is checked per
request, so a downgrade takes effect immediately.
Personal access tokens¶
The straightforward option. Create one under Account → API Tokens
(/accounts/tokens/), choose its scopes, and copy it — it is shown once.
Tokens are prefixed spd_. Give each token the smallest set of
scopes that does its job: a token that can only read is a token
that cannot be used to delete.
A token is a credential, not a config value
Treat it like a password. Keep it out of your repository, out of client-side
JavaScript, and out of URLs — a token in a query string ends up in access
logs and browser history. Put it in an environment variable or a secret
manager, and send it in the Authorization header.
If a token leaks, delete it from the same page. Deletion takes effect at once; there is no cache to wait for.
OAuth2¶
Use this when a tool needs to act for a user who is not you — the user approves the scopes, and you never see their password or their token.
Two flows are supported:
For anything without a browser of its own: a CLI, an editor extension, a terminal on a server.
POST /o/device-authorization/— you get a verification URL and a short code.- The user opens the URL, types the code, and approves the scopes.
- Poll
POST /o/token/until they approve.
The official public client id for the withfeedback CLI is
7yQMsnY2Is2f5tCuwwgItoQu3fRkEX2wnzIRj0Vh. It is public by design —
device-flow clients hold no secret.
For web and mobile apps. PKCE is required; there is no implicit flow and no way to turn PKCE off.
Redirect URI schemes are restricted, and dynamic client registration is not open in production — a client is provisioned deliberately.
Access tokens last one hour. Refresh tokens last 30 days and rotate on every use: the response gives you a new refresh token, and the old one stops working. Store the new one, or your next refresh fails.
JWT¶
curl -X POST https://withfeedback.com/api/auth/token/ \
-H "Content-Type: application/json" \
-d '{"email":"you@example.com","password":"..."}'
Returns an access and a refresh token; refresh at /api/auth/token/refresh/,
revoke at /api/auth/token/revoke/. Reasonable for a front end you own,
unnecessary if a personal access token would do.
Two-factor accounts
If the account has MFA enabled, exchanging a password for a JWT is not the right path — use a personal access token or OAuth2 instead.
What a credential does not decide¶
Authentication says who you are. Two further checks still run on every request, and both can refuse you:
- Your scope — what this particular credential is allowed to touch. See Scopes.
- Your role in the team — owner, admin, member or viewer. A viewer's token
with
moderate:submissionsstill cannot approve anything, because the role does not permit it.
The narrower of the two wins, always. Widening a token's scopes never widens what its owner may do.