Skip to content

API

REST over HTTPS, JSON in and out. Everything the dashboard can do, the API can do, under the same permissions.

https://withfeedback.com/api/v1/

There are two APIs and the difference matters:

Authenticated API Public plane
Prefix /api/v1/ /api/public/v1/
Who calls it your backend, the CLI, an MCP client your visitors' browsers
Credentials token or OAuth2 none — a public key identifies, it never authorizes
Reads anything you have a scope for approved + consented items only
CORS your configured origins any origin

If you are writing server code, you want this section. If you are writing front-end code that posts what a visitor typed, you want the public plane.

The machine-readable contract

The OpenAPI 3 schema is generated from the code itself, so it cannot drift from what the server does:

Schema https://withfeedback.com/api/schema/
Swagger UI https://withfeedback.com/api/docs/
ReDoc https://withfeedback.com/api/redoc/

Every operation has a stable operationId, which is what client generators key on. These pages explain the shape and the rules; the schema is the reference for exact fields.

Resource paths are tenant-scoped

Almost every path carries the team, and usually the project:

GET /api/v1/teams/{team_id}/projects/{project_id}/submissions/

That is deliberate rather than decorative. A resource is only reachable through the team that owns it, so a token belonging to one team cannot read another team's data by guessing an id — the mismatch is a 404, not a 403, because whether a record exists is itself not something to leak.

GET /api/v1/teams/ lists the teams you belong to. GET /api/v1/me/ returns who you are.

Errors

Standard HTTP status codes. The body carries a detail string, and often a machine-readable code.

Status Means What to do
400 The request body is wrong Read the field errors; do not retry unchanged
401 No credentials, or they are invalid Re-authenticate
402 Quota exceeded, or your plan lacks the feature Check code; upgrade or wait for the month to roll
403 Authenticated, but not allowed Wrong scope, or your role lacks the permission
404 Not found — or not yours Check the team and project in the path
409 Conflict with current state Read the state and decide; retrying identically will not help
413 Body too large Send less
429 Rate limited Back off and retry
503 A dependency is unavailable Retry with backoff

402 is not a failure of your code

A 402 means the account ran out of something it pays for, or is on a plan without the feature. The code field says which. Treat it as a state to surface to a human, not an error to retry in a loop.

Idempotency and retries

Anything that spends money or changes state is designed so a retry is safe:

  • Completing a video upload is idempotent — repeat calls return the current state and do not start a second transcode.
  • Submitting a video draft twice reports success and changes nothing; the first submit is the one that counts.
  • Moderation transitions are compare-and-set. Approving something already approved is not an error, and does not fire a second webhook.

Rate limits

Per-IP, and they differ by surface because the surfaces cost different amounts:

Scope Limit
Authenticated requests 1000/hour per user
Unauthenticated 100/hour
Public reads (widget payloads) 120/min
Public writes (submissions, survey responses) 10/min
Public uploads (reserve, complete) 8/min

A 429 carries no penalty beyond the wait. Repeatedly tripping the abuse controls on the public plane is different — see the public plane.

Next

  • Authentication — tokens, OAuth2, and which to pick
  • Scopes — the full list, and how they combine with roles
  • Webhooks — every event, and how to verify a delivery
  • Public plane — the browser-facing half