API
Send feedback from a trusted backend into a Signaldeck form.
Create and revoke account API keys from account settings.
Authenticate each request
Send an account API key in the Authorization header. Keys are for trusted server environments only.
- Use
Authorization: Bearer sig_live_...on both endpoints. - Use a new
Idempotency-Keyfor each submission. - Signaldeck returns
X-Request-Idon every response.
Submit feedback
POST /v1/forms/form_123/submissions creates one submission with one or more answers.
const response = await fetch("https://api.signaldeck.com/v1/forms/form_123/submissions", {
method: "POST",
headers: {
Authorization: "Bearer YOUR_API_KEY",
"Content-Type": "application/json",
"Idempotency-Key": "8ac75c79-8eb8-43c6-8e05-b1952fdf895a",
},
body: JSON.stringify({
"source": "account_closure",
"occurred_at": "2026-09-16T14:30:00Z",
"metadata": { "reason_surface": "settings" },
"answers": [{
"question_id": "question_456",
"type": "text",
"value": "I no longer need this feature."
}]
}),
});
const submission = await response.json();Request fields and limits
Unknown top-level fields are rejected.
answers- One to 25 distinct answers. Each includes
question_id,type, andvalue. source- Optional source label, up to 64 characters.
user_identifier- Optional opaque identifier, up to 255 characters. Omit it for anonymous feedback.
occurred_at- Optional ISO 8601 timestamp with a timezone.
metadata- Optional JSON object: up to 16 KiB, 20 keys across all levels, and three levels deep.
- JSON request bodies are limited to 64 KiB.
- Text answers are limited to 10,000 characters.
- Rating answers must be JSON integers within the configured range.
Look up answerable questions
GET /v1/forms/:formId/schema returns question text, IDs, and answer configuration your backend can render and submit.
GET /v1/forms/form_123/schema
Authorization: Bearer YOUR_API_KEY
{
"form_id": "form_123",
"questions": [
{ "question_id": "question_456", "question": "What should we improve?", "type": "text" },
{ "question_id": "question_789", "question": "How useful is this feature?", "type": "rating", "rating": { "min": 1, "max": 5, "presentation": "stars" } }
]
}Read responses and handle retries
A new submission returns 201 Created. A matching replay returns the original successful response.
{
"form_submission_id": "7e7587ee-9a0a-4d90-a17b-a0992dc4b1fc",
"response_ids": ["e9e2b265-9718-4bc2-8a50-47b9952c50ee"],
"occurred_at": "2026-09-16T14:30:00.000Z",
"created_at": "2026-09-16T14:31:04.000Z"
}- Keep a key for 30 days. Do not reuse it for a different payload.
- A changed payload with the same key returns
409 idempotency_conflict. - Rate limits allow 60 requests per minute per API key. A
429 rate_limitedresponse includesRetry-After. - Each newly created answer consumes one response allowance. A replay does not consume additional allowance.
Protect respondent privacy
Only send identifiers and metadata you need. Do not send email addresses, IP addresses, or other direct identifiers in anonymous feedback. Signaldeck does not log raw answer text or metadata.
Handle errors without exposing feedback
Application-controlled errors use one envelope. Treat code as stable and message as explanatory text.
{
"error": {
"code": "invalid_answer",
"message": "The answer type does not match the question.",
"param": "answers[0].type",
"request_id": "d23fcb6d-b7d8-4892-a7c1-04a3e7207b4c"
}
}Expected codes are invalid_request, invalid_api_key, form_access_denied, idempotency_conflict, invalid_answer, invalid_form, rate_limited, submission_limit_reached, and internal_error.