Official Python SDK for the fdbck API
Project description
fdbck
Official Python SDK for fdbck — a simple API to programmatically collect and structure feedback from your users.
Full type annotations included (PEP 561).
from fdbck import Fdbck
client = Fdbck("sk_fdbck_...")
question = client.questions.create({
"question": "How was your first purchase?",
"type": "rating",
"rating_config": {"min": 1, "max": 5},
"expires_in": 172800,
})
token = client.tokens.create(question["id"], {
"respondent": "user_8f2a",
})
# Send this URL to your user — they answer on fdbck's hosted page
print(token["respond_url"])
# Later, read the results
results = client.questions.results(question["id"])
print(results["data"]) # [{"respondent": "user_8f2a", "value": 5, ...}]
Install
pip install fdbck
Requires Python 3.9 or later. Single dependency: httpx.
Get your API key
Sign up at dashboard.fdbck.sh and grab your API key from the API Keys page. Keys start with sk_fdbck_.
Quick start
The typical workflow is: create a question, generate a token for each respondent, collect their answer, and read the results.
Respondents can answer via fdbck's hosted response page, or directly from your app using a UI SDK (React, Flutter).
1. Create a question
question = client.questions.create({
"question": "How would you rate our onboarding?",
"type": "rating",
"rating_config": {
"min": 1,
"max": 5,
"min_label": "Terrible",
"max_label": "Loved it",
},
"expires_in": 86400, # 24 hours
})
Four question types are available:
| Type | Description | Required fields | Response value |
|---|---|---|---|
yes_no |
Yes or no (options default to ["Yes", "No"]) |
— | "Yes" or "No" |
single_choice |
Pick one option | options |
"Option A" |
multiple_choice |
Pick one or more | options |
["Option A", "Option B"] |
rating |
Numeric scale | rating_config |
4 |
2. Generate tokens
Each respondent gets a unique, single-use token that authorizes them to answer once.
token = client.tokens.create(question["id"], {
"respondent": "user_42", # your internal user ID (optional)
})
# token["respond_url"] → https://fdbck.sh/f/V1StGXR8_Z5jdHi6
# token["expires_at"] → ISO timestamp (1 hour from creation)
Send token["respond_url"] to your user however you like — email, in-app notification, SMS, etc. They open the link, answer on fdbck's hosted response page, and see a confirmation message.
You can also embed the question directly in your app using fdbck-react or fdbck-flutter — pass the token["token"] value to the UI component and it handles submission for you.
3. Read results
Poll for responses at any time — they're available as soon as respondents answer.
results = client.questions.results(question["id"])
for response in results["data"]:
print(response["respondent"], response["value"])
# → "user_42", 5
# Results are paginated — follow the cursor for more
if results["pagination"]["has_more"]:
next_page = client.questions.results(question["id"], {
"cursor": results["pagination"]["next_cursor"],
})
4. Or use webhooks
Get notified in real time instead of polling.
question = client.questions.create({
"question": "How was your experience?",
"type": "yes_no",
"expires_in": 86400,
"webhook_url": "https://myapp.com/hooks/fdbck",
"webhook_trigger": "each_response", # or "expiry", "both"
})
Verify incoming webhooks with the signing secret:
is_valid = client.verify_webhook(raw_body, signature, webhook_secret)
# signature = X-FDBCK-Signature header
# webhook_secret = question["webhook_secret"] from creation response
verify_webhook is also available as a standalone import if you don't need a client instance:
from fdbck import verify_webhook
API reference
Fdbck(api_key, *, base_url=..., timeout=...)
| Option | Type | Default |
|---|---|---|
base_url |
str |
https://api.fdbck.sh |
timeout |
float |
30.0 (seconds) |
Supports context manager usage:
with Fdbck("sk_fdbck_...") as client:
info = client.me()
AsyncFdbck(api_key, *, base_url=..., timeout=...)
Same interface, async. Uses httpx.AsyncClient under the hood.
from fdbck import AsyncFdbck
async with AsyncFdbck("sk_fdbck_...") as client:
question = await client.questions.create({...})
Questions
client.questions.create(params) → Question
Creates a new question.
| Field | Type | Required | Description |
|---|---|---|---|
question |
str |
Yes | The question text (max 500 chars) |
type |
QuestionType |
Yes | yes_no, single_choice, multiple_choice, or rating |
options |
list[str] |
For choice types | 2–20 answer options |
rating_config |
RatingConfig |
For rating |
{"min", "max", "min_label?", "max_label?"} |
expires_in or expires_at |
int or str |
Yes (exactly one) | Seconds from now, or ISO 8601 timestamp |
max_responses |
int |
No | Auto-complete after N responses |
webhook_url |
str |
No | HTTPS URL to receive events |
webhook_trigger |
WebhookTrigger |
No | each_response, expiry, or both |
metadata |
dict[str, str] |
No | Arbitrary key-value pairs |
theme_color |
str |
No | Hex color for response page |
theme_mode |
"light" or "dark" |
No | Response page theme |
hide_branding |
bool |
No | Hide "Powered by fdbck" (paid plans) |
client.questions.get(question_id) → Question
Returns a single question by ID.
client.questions.list(params?) → PaginatedList
Returns a paginated list of questions.
page = client.questions.list({"status": "collecting", "limit": 20})
print(page["data"]) # list[Question]
print(page["pagination"]["has_more"]) # bool
print(page["pagination"]["next_cursor"]) # str | None
| Option | Type | Description |
|---|---|---|
status |
QuestionStatus |
Filter by collecting, completed, expired, or cancelled |
sort |
str |
Sort by created_at or updated_at |
order |
str |
Sort direction: asc or desc |
created_after |
str |
ISO 8601 — only return questions created after this time |
created_before |
str |
ISO 8601 — only return questions created before this time |
limit |
int |
Items per page |
cursor |
str |
Cursor from a previous page's pagination["next_cursor"] |
client.questions.list_all(params?) → Iterator[Question]
Auto-paginates through all questions. Same options as list except cursor.
for question in client.questions.list_all({"status": "collecting"}):
print(question["id"], question["question"])
Async version yields via async for.
client.questions.results(question_id, params?) → QuestionResultsResponse
Returns aggregated results and individual responses for a question.
results = client.questions.results(question["id"], {"limit": 50})
print(results["total_responses"]) # 142
print(results["results"]) # {"average": 4.3, "distribution": {...}}
print(results["type"]) # "rating"
print(results["status"]) # "completed"
for response in results["data"]:
print(response["respondent"]) # "user_42" or None
print(response["value"]) # answer value
print(response["created_at"]) # ISO timestamp
client.questions.cancel(question_id) → Question
Cancels a question and returns the cancelled question. It stops accepting responses immediately.
client.questions.webhooks(question_id, params?) → PaginatedList
Returns webhook delivery logs for a question. Same pagination options as results.
Tokens
client.tokens.create(question_id, params?) → TokenResult
Generates a single-use respondent token.
token = client.tokens.create(question["id"], {
"respondent": "user_42", # optional — your internal ID for this respondent
})
token["token"] # the raw token string
token["respond_url"] # full URL to the hosted response page
token["expires_at"] # ISO timestamp (1 hour from creation)
Account
info = client.me()
# info["user"] → {"id", "email", "name", "avatar_url"}
# info["organization"] → {"id", "name", "slug", "plan", "role", "responses_used", ...}
Error handling
from fdbck import FdbckApiError, FdbckNetworkError
try:
client.questions.create({...})
except FdbckApiError as err:
print(err.status) # 401, 422, etc.
print(err.code) # "unauthorized", "validation_error", etc.
print(err.args[0]) # Human-readable message
print(err.details) # {"fields": [...]} for validation errors
except FdbckNetworkError as err:
print(err) # Connection error, timeout, etc.
print(err.__cause__) # Original exception
Requirements
- Python 3.9+
- An API key from dashboard.fdbck.sh
Links
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file fdbck-0.1.0.tar.gz.
File metadata
- Download URL: fdbck-0.1.0.tar.gz
- Upload date:
- Size: 12.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b0e453a4edbcbe2558050b4e805c4b060dcbb681544f08cd201ea4e3cd5240f6
|
|
| MD5 |
6ca1f45ca988b429e8456d2d46ba4570
|
|
| BLAKE2b-256 |
c01280f853e28d9057f707d1662be438e42ef61ccb7a8df7560e2b9cd7d5d81e
|
File details
Details for the file fdbck-0.1.0-py3-none-any.whl.
File metadata
- Download URL: fdbck-0.1.0-py3-none-any.whl
- Upload date:
- Size: 12.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
85244293e8c8b3f098e4fe17c5b3896a72cdbaa9833eb5ed2197a866fed9483e
|
|
| MD5 |
550b79531e2be552babeea18e4f50a70
|
|
| BLAKE2b-256 |
cdc44cfc84af49474031361ec27561764449bf75cc6b9764bdb3ac2e99d6f82d
|