API Documentation

The Survtapp REST API lets your own systems read the forms in your account, read the responses collected against them, and submit new responses. It speaks JSON over HTTPS and authenticates with an API key you generate in the app.

This page documents every endpoint the API key actually opens. Anything not listed here is part of the Survtapp web application and is not available programmatically — see What the API does not cover.

Before you start

  • API access is a paid-plan feature. Accounts on the Free plan cannot create an API key. Upgrade your plan first, or ask us to enable API access on your account.
  • Only an Account Owner or an Admin can create or view API keys. Managers, Agents and Viewers do not see the API Keys screen.
  • The API is account-scoped. A key only ever reaches data belonging to the account that created it.

Base URL

Every path on this page is relative to:

https://app.survtapp.com

All requests must use HTTPS. Request and response bodies are application/json.

Authentication

Survtapp has three ways in, and only one of them is for you. The web dashboard uses a browser session cookie and the Survtapp mobile app uses a short-lived login token — neither is available to an integration. Third-party integrations authenticate with an API key.

Creating a key

Sign in and go to Settings → API Keys, then choose Create key. Give it a name and, optionally, an expiry date.

The key is shown once, at the moment it is created. Survtapp stores only a hash of it and cannot show it to you again — copy it straight into your secret store. If you lose it, delete the key and create another.

Treat an API key exactly as you would a password. It authenticates as your account. Never commit one to source control, never put one in front-end code, and delete any key you suspect has been exposed.

Sending a key

Keys begin with svt_. Send one in either of these headers — they are equivalent, so pick whichever your HTTP client makes easiest:

Authorization: Bearer svt_your_key_here

x-api-key: svt_your_key_here

When a key stops working

A request is rejected with 401 Unauthorized when the key is unknown, has been deactivated or deleted, has passed its expiry date, or belongs to an account that is no longer active. The same 401 is returned when no key is sent at all.

Endpoints

This is the complete list of endpoints an API key can reach.

Method Path What it does
GET /api/forms Every form in your account, newest change first. Add ?status=PUBLISHED (or DRAFT / ARCHIVED) to filter. Not paginated.
GET /api/forms/{id} One form, including its questions in display order and its branding settings.
GET /api/responses Responses across your account. Paginated and filterable — see Listing responses.
GET /api/responses/{id} One response with all of its answers, plus the form and questions they belong to.
POST /api/responses Submit a completed response to a published form. See Submitting a response.
PATCH /api/responses/{id} Set or clear the lead score (leadScore, a whole number 1–5) and lead note (leadNote, up to 2000 characters) on a response. It does not edit answers.
DELETE /api/responses/{id} Permanently delete a response and its answers. This cannot be undone.

Curly braces mark a path parameter: replace {id} with the record’s id, braces included.

Listing responses

GET /api/responses accepts these query parameters:

Parameter Default Notes
formIdReturn only responses to this form. This is how you list one form’s responses.
statusOne of IN_PROGRESS, COMPLETED, ABANDONED.
page11-based page number.
limit20Page size. Maximum 100; a larger value is rejected with 400.
sortBycreatedAtField to order by.
sortOrderdescasc or desc.

Example

curl -H "Authorization: Bearer svt_your_key_here"   "https://app.survtapp.com/api/responses?formId=FORM_ID&status=COMPLETED&limit=50"

Which returns:

{
  "success": true,
  "data": [
    {
      "id": "resp_...",
      "formId": "form_...",
      "status": "COMPLETED",
      "submittedAt": "2026-08-14T09:12:44.000Z",
      "form": { "id": "form_...", "title": "Site safety check", "type": "CHECKLIST" },
      "collectedBy": { "id": "usr_...", "name": "Dana Reid", "email": "[email protected]" },
      "_count": { "answers": 12 }
    }
  ],
  "pagination": { "page": 1, "limit": 50, "total": 138, "totalPages": 3 }
}

The list view does not include the answers themselves. Fetch GET /api/responses/{id} for those.

Submitting a response

POST /api/responses records a completed response against a form that is published and belongs to your account. Anything else returns 404.

Each answer names a question and puts its value in the field that matches the question’s type — text in textValue, numbers (including ratings, scales and NPS) in numberValue, yes/no in boolValue, an ISO 8601 date in dateValue, and anything structured (multi-select, matrix, file references) in jsonValue.

curl -X POST "https://app.survtapp.com/api/responses"   -H "Authorization: Bearer svt_your_key_here"   -H "Content-Type: application/json"   -d '{
    "formId": "form_...",
    "respondentName": "Dana Reid",
    "respondentEmail": "[email protected]",
    "source": "WEB",
    "submittedAt": "2026-08-14T09:12:44Z",
    "answers": [
      { "questionId": "q_1", "textValue": "All clear" },
      { "questionId": "q_2", "numberValue": 9 },
      { "questionId": "q_3", "boolValue": true },
      { "questionId": "q_4", "jsonValue": ["fire-door", "extinguisher"] }
    ]
  }'

On success the endpoint returns 201 and the new id — not the whole response:

{ "success": true, "data": { "id": "resp_..." } }

Things to know

  • Every required question must be answered. A missing one returns 400 with a missingQuestionIds array naming them.
  • Forms that collect contact details enforce them. Send a contact object keyed by field (for example firstName, email). Waivers always require it.
  • Submissions count against your plan’s response allowance. Once it is used up, submissions are rejected with 402 until the allowance is topped up.
  • Sending the same offlineId twice updates the first response rather than creating a second. Set your own unique offlineId per submission and retries become safe.
  • submittedAt is optional and accepts a UTC or offset timestamp. It preserves the real capture time; without it the server’s clock is used.

Response format

A successful call returns success and data. Endpoints that page add a pagination object; endpoints that do not, do not.

{
  "success": true,
  "data": { },
  "pagination": {        // paginated endpoints only
    "page": 1,
    "limit": 20,
    "total": 138,
    "totalPages": 7
  }
}

An error returns an error message instead, sometimes with a machine-readable code:

{
  "error": "Your account has reached its response limit.",
  "code": "RESPONSE_LIMIT_REACHED",
  "retryable": true
}

Branch on the HTTP status and on code where one is present. Never parse the error string — it is human-readable and is translated into the account’s language.

Errors

Status Code Meaning
400The request body or query string failed validation. error names the offending fields.
401No API key, or a key that is unknown, deactivated or expired.
402RESPONSE_LIMIT_REACHEDThe account’s response allowance is used up. Retryable once topped up — hold the submission and try again rather than discarding it.
403VAULT_MODEThe account has paused collection. Nothing new is accepted until it is resumed from billing settings.
403The key is valid but the action is not permitted, or the share link used has expired or hit its limit.
404No such record in your account. A form that exists but is unpublished, or belongs to another account, also returns 404.
413STORAGE_LIMIT_REACHEDThe account’s file storage allowance is full.
500Something went wrong on our side. Safe to retry.
503MAINTENANCE_MODESurvtapp is briefly down for scheduled maintenance. Honour the Retry-After header and retry.

Webhooks

Polling is rarely what you want. Webhooks push events to you as they happen. Add an endpoint under Settings → Webhooks in the app, choose the events you care about, and copy the signing secret — like an API key, it is shown only once.

Events

Event Sent when
response.completedA response is submitted. Carries the response, its answers, and the form it belongs to.
form.publishedA form is published and starts accepting responses.
form.unpublishedA form is taken offline.
contact.createdA contact is added to your account.
contact.updatedA contact’s details change.

Delivery

Survtapp sends an HTTP POST with a JSON body to your endpoint. The endpoint must be a public HTTPS URL — private, loopback and link-local addresses are refused, and redirects are not followed.

POST https://your-server.example.com/hooks/survtapp
Content-Type: application/json
User-Agent: Survtapp-Webhook/1.0
X-Survtapp-Event: response.completed
X-Survtapp-Timestamp: 2026-08-14T09:12:44.000Z
X-Survtapp-Signature: sha256=9f86d081884c7d659a2f...

{
  "event": "response.completed",
  "timestamp": "2026-08-14T09:12:44.000Z",
  "data": {
    "response": {
      "id": "resp_...",
      "formId": "form_...",
      "status": "COMPLETED",
      "completedAt": "2026-08-14T09:12:44.000Z",
      "respondentName": "Dana Reid",
      "respondentEmail": "[email protected]",
      "answers": [ { "questionId": "q_1", "value": "All clear" } ]
    },
    "form": { "id": "form_...", "title": "Site safety check", "type": "CHECKLIST" }
  }
}

Verifying the signature

Every delivery is signed with HMAC-SHA256 over the raw request body, using your webhook secret. The header value is the string sha256= followed by the hex digest — include that prefix when you compare. Verify before you trust a payload.

import crypto from 'crypto';

function verify(rawBody, header, secret) {
  const expected =
    'sha256=' +
    crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
  const a = Buffer.from(header);
  const b = Buffer.from(expected);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

Responding

  • Return a 2xx within 10 seconds. Slower than that and the delivery is abandoned and recorded as failed. Do heavy work asynchronously.
  • Failed deliveries are not retried automatically. They are logged against the webhook so you can see them, but treat a missed event as lost and reconcile with GET /api/responses if the data matters.
  • Make your handler idempotent — key it on the record id inside data.
  • Keep the secret confidential, and rotate it by replacing the webhook if it is ever exposed.

What the API does not cover

We would rather tell you this up front than have you discover it mid-build. The following are available in the Survtapp web application but not through an API key:

  • Creating or editing forms. Build forms in the form builder; the API reads them.
  • Contacts. Reading, creating and importing contacts is web-only. Contact changes can still be pushed to you as webhook events, and Survtapp can sync contacts outward to Mailchimp, SendGrid and HubSpot under Settings → Integrations.
  • Analytics and reporting. Charts, summaries and AI insights are produced in the app. Compute your own from GET /api/responses/{id}, or export CSV, XLSX and PDF from the Reports screen.
  • Users, teams, billing, events and waivers. Administered in the app only.

If one of these blocks an integration you are building, tell us — what customers actually need is what we prioritise.

Need a hand?

Send us the endpoint you are calling and the status you are getting back, and we will look at it with you: [email protected].