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.
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 |
|---|---|---|
| formId | — | Return only responses to this form. This is how you list one form’s responses. |
| status | — | One of IN_PROGRESS, COMPLETED, ABANDONED. |
| page | 1 | 1-based page number. |
| limit | 20 | Page size. Maximum 100; a larger value is rejected with 400. |
| sortBy | createdAt | Field to order by. |
| sortOrder | desc | asc 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
missingQuestionIdsarray naming them. - Forms that collect contact details enforce them. Send a
contactobject keyed by field (for examplefirstName,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
offlineIdtwice updates the first response rather than creating a second. Set your own uniqueofflineIdper submission and retries become safe. submittedAtis 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 |
|---|---|---|
| 400 | — | The request body or query string failed validation. error names the offending fields. |
| 401 | — | No API key, or a key that is unknown, deactivated or expired. |
| 402 | RESPONSE_LIMIT_REACHED | The account’s response allowance is used up. Retryable once topped up — hold the submission and try again rather than discarding it. |
| 403 | VAULT_MODE | The account has paused collection. Nothing new is accepted until it is resumed from billing settings. |
| 403 | — | The key is valid but the action is not permitted, or the share link used has expired or hit its limit. |
| 404 | — | No such record in your account. A form that exists but is unpublished, or belongs to another account, also returns 404. |
| 413 | STORAGE_LIMIT_REACHED | The account’s file storage allowance is full. |
| 500 | — | Something went wrong on our side. Safe to retry. |
| 503 | MAINTENANCE_MODE | Survtapp 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.completed | A response is submitted. Carries the response, its answers, and the form it belongs to. |
| form.published | A form is published and starts accepting responses. |
| form.unpublished | A form is taken offline. |
| contact.created | A contact is added to your account. |
| contact.updated | A 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
2xxwithin 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/responsesif 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].
