Complete reference for all Sayify.pro API endpoints with examples and response payloads.
📡 API Endpoints Reference
Base URL: https://sayify.pro/api/v1/
All endpoints require Bearer token authentication. See Authentication.
🔗 Links
POST /v1/links/ — Create Feedback Link
Create a new response collection link with custom questions. Returns 201 on success. All 11 question types are supported inline.
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | ✅ | Display name for the feedback link. |
slug |
string | — | URL slug — auto-generated from name if omitted. Must be unique per workspace. |
status |
string | — | "active" (default) or "paused". |
settings.description |
string | — | Shown on the public intake page header. |
settings.thankYouMessage |
string | — | Shown after submission (default: "Thanks for your response!"). |
settings.redirectUrl |
string | — | Redirect respondent to this URL after submission. |
settings.tts_enabled |
boolean | — | Read questions aloud using text-to-speech (default: false). |
settings.live_transcription |
boolean | — | Show live transcription while recording voice (default: false). |
questions |
array | — | Array of question objects (see question fields below). |
on_finished.webhook_url |
string | — | Auto-create a webhook that fires on response submission. |
on_finished.webhook_secret |
string | — | Secret for webhook signature verification. |
Question Fields:
| Field | Type | Required | Description |
|---|---|---|---|
prompt |
string | ✅ | The question text shown to the respondent. |
type |
string | — | Response type (default: "voice"). See Question Types. |
field_key |
string | — | snake_case identifier in structured output — auto-generated from prompt if omitted. |
order |
integer | — | Display order (0-indexed, default: 0). |
required |
boolean | — | Whether the question must be answered (default: false). |
configuration |
object | — | Type-specific settings (see Question Types). |
condition |
object | — | Conditional visibility: { field, operator, value }. See Conditional Logic. |
enable_dynamic_probing |
boolean | — | AI follow-up if answer is unclear (default: false). |
fallback_to_text |
boolean | — | Allow text fallback for voice questions (default: true). |
evaluator_config |
object | — | { clarity_threshold, max_follow_ups, custom_fields } — used when probing is on. |
Example (cURL):
curl -X POST https://sayify.pro/api/v1/links/ \
-H "Authorization: Bearer sk_live_AbCdEfGhIjKlMnOpQrStUvWxYz0123456789xyz" \
-H "Content-Type: application/json" \
-d '{
"name": "Product Feedback Survey",
"slug": "product-feedback-survey",
"status": "active",
"settings": {
"description": "Tell us about your experience with our product.",
"thankYouMessage": "Thanks for your feedback!",
"redirectUrl": "https://example.com/thank-you"
},
"questions": [
{
"prompt": "Tell us about your experience",
"type": "voice",
"field_key": "experience",
"order": 0,
"required": true,
"configuration": { "max_duration": 120, "enable_ai_evaluation": true },
"enable_dynamic_probing": true,
"evaluator_config": { "clarity_threshold": 60, "max_follow_ups": 2, "custom_fields": ["Summary"] }
},
{
"prompt": "How likely are you to recommend us? (1-10)",
"type": "number",
"field_key": "nps_score",
"order": 1,
"required": true,
"configuration": { "min": 1, "max": 10, "step": 1 }
},
{
"prompt": "What is your email address?",
"type": "email",
"field_key": "email_address",
"order": 2,
"required": true
}
],
"on_finished": {
"webhook_url": "https://example.com/webhook",
"webhook_secret": "my-webhook-secret"
}
}'
Example (JavaScript):
const payload = {
name: "Product Feedback Survey",
slug: "product-feedback-survey",
status: "active",
settings: {
description: "Tell us about your experience with our product.",
thankYouMessage: "Thanks for your feedback!",
redirectUrl: "https://example.com/thank-you"
},
questions: [
{
prompt: "Tell us about your experience",
type: "voice", field_key: "experience", order: 0, required: true,
configuration: { max_duration: 120, enable_ai_evaluation: true },
enable_dynamic_probing: true,
evaluator_config: { clarity_threshold: 60, max_follow_ups: 2, custom_fields: ["Summary"] }
},
{
prompt: "How likely are you to recommend us? (1-10)",
type: "number", field_key: "nps_score", order: 1, required: true,
configuration: { min: 1, max: 10, step: 1 }
},
{
prompt: "What is your email address?",
type: "email", field_key: "email_address", order: 2, required: true
}
],
on_finished: {
webhook_url: "https://example.com/webhook",
webhook_secret: "my-webhook-secret"
}
};
const response = await fetch("https://sayify.pro/api/v1/links/", {
method: "POST",
headers: {
"Authorization": "Bearer sk_live_AbCdEfGhIjKlMnOpQrStUvWxYz0123456789xyz",
"Content-Type": "application/json"
},
body: JSON.stringify(payload)
});
const link = await response.json();
console.log(link.uuid, link.public_url); // 201 Created
Example (Python):
import requests
payload = {
"name": "Product Feedback Survey",
"slug": "product-feedback-survey",
"status": "active",
"settings": {
"description": "Tell us about your experience with our product.",
"thankYouMessage": "Thanks for your feedback!",
"redirectUrl": "https://example.com/thank-you"
},
"questions": [
{
"prompt": "Tell us about your experience",
"type": "voice", "field_key": "experience", "order": 0, "required": True,
"configuration": {"max_duration": 120, "enable_ai_evaluation": True},
"enable_dynamic_probing": True,
"evaluator_config": {"clarity_threshold": 60, "max_follow_ups": 2, "custom_fields": ["Summary"]}
},
{
"prompt": "How likely are you to recommend us? (1-10)",
"type": "number", "field_key": "nps_score", "order": 1, "required": True,
"configuration": {"min": 1, "max": 10, "step": 1}
},
{
"prompt": "What is your email address?",
"type": "email", "field_key": "email_address", "order": 2, "required": True
}
],
"on_finished": {
"webhook_url": "https://example.com/webhook",
"webhook_secret": "my-webhook-secret"
}
}
response = requests.post(
"https://sayify.pro/api/v1/links/",
headers={
"Authorization": "Bearer sk_live_AbCdEfGhIjKlMnOpQrStUvWxYz0123456789xyz",
"Content-Type": "application/json"
},
json=payload
)
link = response.json()
print(link["uuid"], link["public_url"]) # 201 Created
Response (201 Created):
{
"uuid": "ea92fd15-7563-447f-a75d-0d61570f4c73",
"name": "Product Feedback Survey",
"slug": "product-feedback-survey",
"status": "active",
"settings": {
"description": "Tell us about your experience with our product.",
"thankYouMessage": "Thanks for your feedback!",
"redirectUrl": "https://example.com/thank-you"
},
"questions": [
{
"uuid": "4041f961-328f-4c6d-a5d3-2125a80f1ec5",
"prompt": "Tell us about your experience",
"field_key": "experience",
"response_type": "voice",
"configuration": { "max_duration": 120, "enable_ai_evaluation": true },
"order": 0,
"required": true,
"condition": {},
"enable_dynamic_probing": true,
"fallback_to_text": true,
"evaluator_config": {
"clarity_threshold": 60,
"max_follow_ups": 2,
"custom_fields": ["Summary"]
},
"date_created": "2026-02-22T17:15:55.291121Z",
"date_updated": "2026-02-22T17:15:55.291131Z"
},
{
"uuid": "f4369f61-cdf8-423d-a9d0-5bf74c61761b",
"prompt": "How likely are you to recommend us? (1–10)",
"field_key": "nps_score",
"response_type": "number",
"configuration": { "min": 1, "max": 10, "step": 1 },
"order": 1,
"required": true,
"condition": {},
"enable_dynamic_probing": false,
"fallback_to_text": true,
"evaluator_config": {},
"date_created": "2026-02-22T17:15:55.295912Z",
"date_updated": "2026-02-22T17:15:55.295920Z"
},
{
"uuid": "1eb6072c-0f56-457c-8c12-8a74f51edef7",
"prompt": "What is your email address?",
"field_key": "email_address",
"response_type": "email",
"configuration": {},
"order": 2,
"required": true,
"condition": {},
"enable_dynamic_probing": false,
"fallback_to_text": true,
"evaluator_config": {},
"date_created": "2026-02-22T17:15:55.297838Z",
"date_updated": "2026-02-22T17:15:55.297848Z"
}
],
"response_count": 0,
"public_url": "https://acme-corp.sayify.pro/i/product-feedback-survey",
"date_created": "2026-02-22T17:15:55.289912Z",
"date_updated": "2026-02-22T17:15:55.289922Z"
}
Possible Errors:
| Status | Code | Description |
|---|---|---|
400 |
NAME_REQUIRED |
The name field is missing from the request body. |
400 |
SLUG_CONFLICT |
The provided slug already exists — omit it to auto-generate. |
403 |
LINK_LIMIT_REACHED |
Workspace has reached its plan link quota. |
403 |
INSUFFICIENT_SCOPE |
API key missing scope: links:write. |
GET /v1/links/ — List All Links
Retrieve all feedback links in your workspace.
cURL:
curl https://sayify.pro/api/v1/links/ \
-H "Authorization: Bearer sk_live_AbCdEfGhIjKlMnOpQrStUvWxYz0123456789xyz"
JavaScript:
const response = await fetch("https://sayify.pro/api/v1/links/", {
headers: { "Authorization": "Bearer sk_live_AbCdEfGhIjKlMnOpQrStUvWxYz0123456789xyz" }
});
const links = await response.json();
links.forEach(link => console.log(link.name, link.public_url));
Python:
import requests
response = requests.get(
"https://sayify.pro/api/v1/links/",
headers={"Authorization": "Bearer sk_live_AbCdEfGhIjKlMnOpQrStUvWxYz0123456789xyz"}
)
links = response.json()
for link in links:
print(link["name"], link["public_url"])
Response (200 OK):
[
{
"uuid": "f87ff124-d1ca-4dd2-b440-b9e719931a2b",
"name": "Product Feedback Survey",
"slug": "product-feedback-survey",
"status": "active",
"settings": {
"description": "Tell us about your experience with our product.",
"redirectUrl": "https://example.com/thank-you",
"thankYouMessage": "Thanks for your feedback!"
},
"questions": [ "..." ],
"response_count": 30,
"public_url": "https://acme-corp.sayify.pro/i/product-feedback-survey",
"date_created": "2026-02-17T07:39:11.883452Z",
"date_updated": "2026-02-19T17:06:15.899034Z"
}
]
GET /v1/links/{link_uuid}/ — Get Link Details
Retrieve a single feedback link with all its questions.
| Parameter | Type | Required | Description |
|---|---|---|---|
link_uuid |
uuid | ✅ | UUID of the link (from the create response). |
cURL:
curl https://sayify.pro/api/v1/links/f87ff124-d1ca-4dd2-b440-b9e719931a2b/ \
-H "Authorization: Bearer sk_live_AbCdEfGhIjKlMnOpQrStUvWxYz0123456789xyz"
JavaScript:
const linkUuid = "f87ff124-d1ca-4dd2-b440-b9e719931a2b";
const response = await fetch(`https://sayify.pro/api/v1/links/${linkUuid}/`, {
headers: { "Authorization": "Bearer sk_live_AbCdEfGhIjKlMnOpQrStUvWxYz0123456789xyz" }
});
const link = await response.json();
console.log(`${link.name} — ${link.questions.length} questions, ${link.response_count} responses`);
Python:
import requests
link_uuid = "f87ff124-d1ca-4dd2-b440-b9e719931a2b"
response = requests.get(
f"https://sayify.pro/api/v1/links/{link_uuid}/",
headers={"Authorization": "Bearer sk_live_AbCdEfGhIjKlMnOpQrStUvWxYz0123456789xyz"}
)
link = response.json()
print(f"{link['name']} — {len(link['questions'])} questions, {link['response_count']} responses")
Returns the full link object with all questions, settings, and response count (same shape as the create response).
Possible Errors:
| Status | Code | Description |
|---|---|---|
403 |
INSUFFICIENT_SCOPE |
API key missing scope: links:read. |
404 |
— | No link with that UUID exists in this workspace. |
DELETE /v1/links/{link_uuid}/ — Delete Feedback Link
Permanently delete a feedback link and all its questions. This cannot be undone. Response data collected through this link is retained.
| Parameter | Type | Required | Description |
|---|---|---|---|
link_uuid |
uuid | ✅ | UUID of the link to delete. |
cURL:
curl -X DELETE https://sayify.pro/api/v1/links/0b4d51a8-0a69-48bf-8113-fc9940efa9c0/ \
-H "Authorization: Bearer sk_live_AbCdEfGhIjKlMnOpQrStUvWxYz0123456789xyz"
JavaScript:
const linkUuid = "0b4d51a8-0a69-48bf-8113-fc9940efa9c0";
const response = await fetch(`https://sayify.pro/api/v1/links/${linkUuid}/`, {
method: "DELETE",
headers: { "Authorization": "Bearer sk_live_AbCdEfGhIjKlMnOpQrStUvWxYz0123456789xyz" }
});
const result = await response.json();
console.log(result.message); // "Feedback link deleted successfully"
Python:
import requests
link_uuid = "0b4d51a8-0a69-48bf-8113-fc9940efa9c0"
response = requests.delete(
f"https://sayify.pro/api/v1/links/{link_uuid}/",
headers={"Authorization": "Bearer sk_live_AbCdEfGhIjKlMnOpQrStUvWxYz0123456789xyz"}
)
result = response.json()
print(result["message"]) # "Feedback link deleted successfully"
Response (200 OK):
{
"message": "Feedback link deleted successfully"
}
Possible Errors:
| Status | Code | Description |
|---|---|---|
403 |
INSUFFICIENT_SCOPE |
API key missing scope: links:write. |
404 |
— | No link with that UUID exists in this workspace. |
📊 Responses
GET /v1/links/{link_uuid}/responses/ — List Responses
Retrieve all responses collected for a specific feedback link.
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
page |
integer | 1 |
Page number. |
page_size |
integer | 20 |
Results per page. |
ordering |
string | -submitted_at |
Sort field. |
search |
string | — | Search in email or transcript. |
Response (200 OK):
{
"count": 42,
"next": "https://sayify.pro/api/v1/links/{link_uuid}/responses/?page=2",
"previous": null,
"results": [
{
"uuid": "resp-uuid-1",
"session_id": "sess-uuid-1",
"answers": [
{
"uuid": "ans-uuid-1",
"question_text": "Tell us about your experience",
"audio_url": "https://resource.sayify.pro/responses/abc123/audio.mp3",
"transcription": "I really love the voice recording feature!",
"duration": 5.2,
"ai_insights": {
"sentiment": "positive",
"sentiment_score": 0.92,
"actionable": true,
"key_topics": ["voice recording", "product feedback"],
"summary": "User expresses strong satisfaction with voice recording"
}
}
],
"respondent_email": "user@example.com",
"submitted_at": "2026-02-10T13:30:00Z",
"status": "completed"
}
]
}
GET /v1/links/{link_uuid}/responses/{response_uuid}/ — Get Response Detail
Returns the full detail of a single response including all answers and AI insights.
🔔 Webhooks
GET /v1/workspaces/{workspace_uuid}/webhooks/ — List Webhooks
Returns all webhooks configured in your workspace.
POST /v1/workspaces/{workspace_uuid}/webhooks/ — Create Webhook
| Field | Type | Required | Description |
|---|---|---|---|
url |
string | ✅ | Your webhook endpoint URL (must be HTTPS). |
events |
array | ✅ | Event types: ["response.started", "response.submitted"]. |
enabled |
boolean | — | Whether webhook is active (default: true). |
DELETE /v1/workspaces/{workspace_uuid}/webhooks/{webhook_uuid}/ — Delete Webhook
Permanently removes the webhook.
[!NOTE]
For detailed webhook payload examples, event documentation, and retry policy, see Webhooks.
🔑 API Tokens
GET /v1/tokens/ — List Tokens
Returns all API tokens in your workspace.
POST /v1/tokens/ — Create Token
{ "name": "Production Key" }
Returns the token with the full API key (shown only once).
DELETE /v1/tokens/{token_uuid}/ — Revoke Token
Immediately revokes the token. Any in-flight requests using this key will fail.
📝 Question Types
Sayify supports 11 response types. Use the type field when creating questions via the API.
| Type | Description | Configuration |
|---|---|---|
voice |
Voice recording with optional AI evaluation. | { max_duration, enable_ai_evaluation } |
voice_text_fallback |
Voice with text fallback if mic unavailable. | { max_duration, max_characters } |
text |
Free text input. | {} |
multiple_choice |
Single or multi-select from predefined options. | { options: [...], allow_multiple } |
number |
Numeric input with optional min/max. | { min, max, step } |
rating |
Star, emoji, NPS, or numeric scale. | { scale_type, max_value } |
image_choice |
Visual card selection with images/videos. | { cards: [{ id, label, media_url, media_type }] } |
email |
Email address input with validation. | {} |
date |
Date picker. | {} |
yes_no |
Boolean yes/no toggle. | {} |
file_upload |
File attachment upload. | { allowed_types: [...], max_size_mb } |
Configuration Examples
Voice with AI probing:
{
"prompt": "Tell us about your experience",
"type": "voice",
"field_key": "experience",
"configuration": { "max_duration": 120, "enable_ai_evaluation": true },
"enable_dynamic_probing": true,
"evaluator_config": {
"clarity_threshold": 60,
"max_follow_ups": 2,
"custom_fields": ["Summary"]
}
}
Voice with text fallback:
{
"prompt": "Describe any issues you faced",
"type": "voice_text_fallback",
"field_key": "issues",
"configuration": { "max_duration": 120, "max_characters": 500 }
}
Multiple choice:
{
"prompt": "How did you hear about us?",
"type": "multiple_choice",
"field_key": "referral_source",
"configuration": {
"options": ["Google", "Friend", "Social Media", "Other"],
"allow_multiple": false
}
}
Number (NPS):
{
"prompt": "How likely are you to recommend us? (1-10)",
"type": "number",
"field_key": "nps_score",
"configuration": { "min": 1, "max": 10, "step": 1 }
}
Rating (star/emoji/NPS scale):
{
"prompt": "Rate your experience",
"type": "rating",
"field_key": "experience_rating",
"configuration": {
"scale_type": "stars",
"max_value": 5
}
}
scale_typeoptions:"stars"(⭐ default),"emojis"(😡😞😐😊🤩),"nps"(0–10),"numbers"(plain numeric).Webhook payloads include
rating_details: { value, max_value, scale_type }for this type.
Image choice (visual cards):
{
"prompt": "Select your preferred design",
"type": "image_choice",
"field_key": "preferred_design",
"configuration": {
"cards": [
{
"id": "card-uuid-1",
"label": "Modern Blue",
"media_url": "https://example.com/blue.jpg",
"media_type": "image"
},
{
"id": "card-uuid-2",
"label": "Classic Red",
"media_url": "https://example.com/red.mp4",
"media_type": "video"
}
]
}
}
media_typecan be"image"or"video". Webhook payloads includeselected_card: { card_id, label, media_url, media_type }for this type.
File upload:
{
"prompt": "Upload your resume",
"type": "file_upload",
"field_key": "resume",
"configuration": {
"allowed_types": ["pdf", "docx"],
"max_size_mb": 5
}
}
🔀 Conditional Logic
Control question visibility based on previous answers. Add a condition object to any question.
| Operator | Description | Example |
|---|---|---|
less_than |
Show if numeric value is below threshold. | Show "What went wrong?" if rating < 7. |
equals |
Show if value matches exactly. | Show "T-shirt size?" if attendance_type == "In-Person". |
contains |
Show if value contains a keyword. | Show "Tell us more" if issue_type contains "bug". |
Example — Show follow-up if rating is low:
{
"prompt": "What went wrong?",
"type": "voice",
"field_key": "negative_feedback",
"condition": {
"show_if": "field_value",
"field": "rating",
"operator": "less_than",
"value": 7
}
}
Example — Show based on choice:
{
"prompt": "Your t-shirt size?",
"type": "multiple_choice",
"field_key": "tshirt_size",
"condition": {
"show_if": "field_value",
"field": "attendance_type",
"operator": "equals",
"value": "In-Person"
},
"configuration": {
"options": ["XS", "S", "M", "L", "XL", "2XL"]
}
}
Example — Show if answer mentions keyword:
{
"prompt": "Tell us more about the bug",
"type": "voice_text_fallback",
"field_key": "bug_details",
"condition": {
"show_if": "field_value",
"field": "issue_type",
"operator": "contains",
"value": "bug"
}
}
🎓 What's Next?
- API Overview — Base URL, request format, pagination, rate limits.
- Authentication — API key creation and security.
- Errors — Error codes and retry strategy.
- Webhooks — Event payloads, retry policy, and delivery logs.