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-Key for each submission.
  • Signaldeck returns X-Request-Id on every response.

Submit feedback

POST /v1/forms/form_123/submissions creates one submission with one or more answers.

JavaScript
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, and value.
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.

Bash
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.

JSON
{
  "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_limited response includes Retry-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.

JSON
{
  "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.