Survey Tests
Test an SMS survey end to end. Survey tests run the other way round from a call test: your survey platform sends the survey, and a TotalPath respondent answers it like a real customer.
10 endpoints
How a survey test runs
- Rent a survey number once. Each test you run at the same time needs its own number; see Numbers.
- Save a respondent (a survey test config): who the person is, how they feel and how they behave.
- Start a test, naming the number your survey platform sends from. The response gives
agentE164, the number to send the survey to. - Send your survey to that number. The respondent answers by SMS like a real customer. Each reply it sends is billed per SMS segment; messages your survey sends are free.
- Read the result: what happened (
outcome) and the whole conversation.
Create a survey test config
/testing/survey-test-configsSave a reusable survey respondent: who the person is, how they feel and how they behave. Saving a config makes nothing answerable; start a survey test with it to do that. A config never names a number, and sending `phoneNumber` is a 400. Needs the `testing:write` scope.
Body Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| name | string | required | Display name, up to 100 characters |
| description | string | optional | Up to 500 characters |
| sector | string | required | What the survey is about, e.g. customer satisfaction or meter read. You name it |
| mission | string | required | What this person is doing and how they feel about it |
| acceptance | string | required | What a good survey looks like |
| scenario | string | required | How the respondent behaves once the survey is under way: `happy-path`, `out-of-range`, `changes-mind`, `abandons` or `asks-question-back`. Answers are never scripted; the respondent maps who it is onto whatever scale the survey presents |
| profileId | string | optional | A saved profile to use as the persona (see Profiles) |
| expectedTurns | number | optional | How many questions the survey asks, 1-50. The only limit that is enforced: after this many answers the conversation is closed at the carrier, so the closing "thanks for your feedback" gets no reply. Leave it out and nothing is enforced |
| tags | string[] | optional | Up to 10 tags |
| voiceId | string | optional | Voice surveys only; ignored on an SMS conversation |
Request
curl -X POST -H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"Unhappy after engineer visit - abandons","sector":"customer satisfaction","mission":"You had an engineer visit on Tuesday and it went badly.","acceptance":"The survey records the answers given before the respondent stopped.","scenario":"abandons","expectedTurns":4}' \
https://api.nopaque.co.uk/testing/survey-test-configsResponse
{
"id": "4c0f1b8e-6a7d-4e2b-9f0c-2d5a8b7e1c34",
"workspaceId": "ws_abc123",
"name": "Unhappy after engineer visit - abandons",
"description": "Checks that partial survey answers are recorded",
"sector": "customer satisfaction",
"mission": "You had an engineer visit on Tuesday and it went badly.",
"acceptance": "The survey records the answers given before the respondent stopped.",
"profileId": "prof_abc123",
"expectedTurns": 4,
"scenario": "abandons",
"tags": [
"nps"
],
"createdAt": "2026-11-02T10: 00: 00Z",
"updatedAt": "2026-11-02T10: 00: 00Z"
}List survey test configs
/testing/survey-test-configsEvery saved survey respondent in the workspace, in one response (not paginated). List items leave out the long `mission` and `acceptance` text; get one config for those.
Request
curl -H "x-api-key: YOUR_API_KEY" \
https://api.nopaque.co.uk/testing/survey-test-configsResponse
{
"configs": [
{
"id": "4c0f1b8e-6a7d-4e2b-9f0c-2d5a8b7e1c34",
"workspaceId": "ws_abc123",
"name": "Unhappy after engineer visit - abandons",
"description": "Checks that partial survey answers are recorded",
"sector": "customer satisfaction",
"profileId": "prof_abc123",
"expectedTurns": 4,
"scenario": "abandons",
"tags": [
"nps"
],
"createdAt": "2026-11-02T10: 00: 00Z",
"updatedAt": "2026-11-02T10: 00: 00Z"
}
]
}Get a survey test config
/testing/survey-test-configs/{id}One saved respondent, including the `mission` and `acceptance` text the list leaves out. 404 when it does not exist or belongs to another workspace.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | required | Survey test config ID |
Request
curl -H "x-api-key: YOUR_API_KEY" \
https://api.nopaque.co.uk/testing/survey-test-configs/4c0f1b8e-6a7d-4e2b-9f0c-2d5a8b7e1c34Response
{
"id": "4c0f1b8e-6a7d-4e2b-9f0c-2d5a8b7e1c34",
"workspaceId": "ws_abc123",
"name": "Unhappy after engineer visit - abandons",
"description": "Checks that partial survey answers are recorded",
"sector": "customer satisfaction",
"mission": "You had an engineer visit on Tuesday and it went badly.",
"acceptance": "The survey records the answers given before the respondent stopped.",
"profileId": "prof_abc123",
"expectedTurns": 4,
"scenario": "abandons",
"tags": [
"nps"
],
"createdAt": "2026-11-02T10: 00: 00Z",
"updatedAt": "2026-11-02T10: 00: 00Z"
}Update a survey test config
/testing/survey-test-configs/{id}Partial update: send only the fields to change, at least one (an empty body is a 400). `null` clears `description`, `tags` or `voiceId`; `scenario` cannot be cleared. Tests already started are unaffected, because starting a test freezes what it needs.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | required | Survey test config ID |
Body Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| name | string | optional | Up to 100 characters |
| description | string | null | optional | Up to 500 characters; null clears it |
| sector | string | optional | What the survey is about |
| mission | string | optional | Who the respondent is and how they feel |
| acceptance | string | optional | What a good survey looks like |
| scenario | string | optional | How the respondent behaves once the survey is under way: `happy-path`, `out-of-range`, `changes-mind`, `abandons` or `asks-question-back`. Answers are never scripted; the respondent maps who it is onto whatever scale the survey presents |
| profileId | string | optional | A saved profile to use as the persona |
| expectedTurns | number | optional | How many questions the survey asks, 1-50 |
| tags | string[] | null | optional | Up to 10 tags; null clears them |
| voiceId | string | null | optional | Voice surveys only; null clears it |
Request
curl -X PATCH -H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"scenario":"asks-question-back"}' \
https://api.nopaque.co.uk/testing/survey-test-configs/4c0f1b8e-6a7d-4e2b-9f0c-2d5a8b7e1c34Response
{
"id": "4c0f1b8e-6a7d-4e2b-9f0c-2d5a8b7e1c34",
"workspaceId": "ws_abc123",
"name": "Unhappy after engineer visit - abandons",
"description": "Checks that partial survey answers are recorded",
"sector": "customer satisfaction",
"mission": "You had an engineer visit on Tuesday and it went badly.",
"acceptance": "The survey records the answers given before the respondent stopped.",
"profileId": "prof_abc123",
"expectedTurns": 4,
"scenario": "asks-question-back",
"tags": [
"nps"
],
"createdAt": "2026-11-02T10: 00: 00Z",
"updatedAt": "2026-11-03T09: 00: 00Z"
}Delete a survey test config
/testing/survey-test-configs/{id}Delete a saved respondent. Tests already started from it, and their results, are kept. Returns 204 with no body.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | required | Survey test config ID |
Request
curl -X DELETE -H "x-api-key: YOUR_API_KEY" \
https://api.nopaque.co.uk/testing/survey-test-configs/4c0f1b8e-6a7d-4e2b-9f0c-2d5a8b7e1c34Start a survey test
/testing/survey-runsStart waiting for one survey, and get back the number to send it to. Nothing is sent by TotalPath at this point: the test claims one of your live survey numbers (see Numbers) for the pair of that number and the sender you declare, and freezes the respondent onto the test. Send your survey from `endUserE164` to the returned `agentE164` before `expiresAt` (epoch seconds); if nothing arrives the result is `no_survey_arrived`. This costs money once the survey arrives: the respondent replies by real SMS, and every reply segment it sends is billed (messages your survey sends to us are free). The test holds the number for its whole window. 409 when the workspace owns no live survey numbers, or every one is already running a test against this sender; the message says which. Needs the `testing:write` scope.
Body Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| configId | string | required | A saved survey test config in this workspace |
| endUserE164 | string | required | The number your survey will be sent from, in E.164. Must not be a number in this TotalPath account |
| windowSecs | number | optional | How long to wait for the survey, 60-43200 seconds. Default 300. Keep it short: the window holds one of your numbers |
| expectedTurns | number | optional | Overrides the config's question count for this test, 1-50 |
| maxMessages | number | optional | Loop-safety cap written into the respondent's instructions, 1-200. Default 40. Not enforced; use expectedTurns for a hard limit |
Request
curl -X POST -H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"configId":"4c0f1b8e-6a7d-4e2b-9f0c-2d5a8b7e1c34","endUserE164":"+447700900123","windowSecs":300}' \
https://api.nopaque.co.uk/testing/survey-runsResponse
{
"runId": "9b2e4f6a-1c3d-4e5f-8a7b-6c5d4e3f2a1b",
"configId": "4c0f1b8e-6a7d-4e2b-9f0c-2d5a8b7e1c34",
"agentE164": "+447457416494",
"endUserE164": "+447700900123",
"state": "waiting",
"startedAt": "2026-11-02T10: 05: 00Z",
"expiresAt": 1793614200,
"maxMessages": 40
}List survey tests
/testing/survey-runsEvery survey test holding a number, plus how many survey numbers are free. A test occupies one number for its window, so `numbers.free` is what to check before starting another. Not paginated. Judge whether a test is live by `live`, not by it being listed.
Request
curl -H "x-api-key: YOUR_API_KEY" \
https://api.nopaque.co.uk/testing/survey-runsResponse
{
"runs": [
{
"runId": "9b2e4f6a-1c3d-4e5f-8a7b-6c5d4e3f2a1b",
"configId": "4c0f1b8e-6a7d-4e2b-9f0c-2d5a8b7e1c34",
"agentE164": "+447457416494",
"endUserE164": "+447700900123",
"state": "in_progress",
"startedAt": "2026-11-02T10: 05: 00Z",
"engagedAt": "2026-11-02T10: 06: 10Z",
"channel": "sms_chat",
"expiresAt": 1793614200,
"live": true,
"expectedTurns": 4,
"answersGiven": 1
}
],
"numbers": {
"total": 2,
"busy": 1,
"free": 1
}
}Stop a survey test
/testing/survey-runs/{id}End a test before its window closes and free its number for another test. A survey arriving afterwards gets no answer. The result is kept, with outcome `stopped`. Returns 204 with no body.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | required | Survey run ID (`runId`), as returned when the test was started |
Request
curl -X DELETE -H "x-api-key: YOUR_API_KEY" \
https://api.nopaque.co.uk/testing/survey-runs/9b2e4f6a-1c3d-4e5f-8a7b-6c5d4e3f2a1bList survey results
/testing/survey-resultsWhat happened in each survey test, newest first. Results are kept after a test ends, however it ended. `outcome` says what happened, not whether the survey passed: `completed` (every expected question asked and answered), `partial`, `ended` (no question count was set), `no_survey_arrived`, `stopped` or `start_failed`. The conversation itself is left out; get one result for it. Page with `cursor` until `nextCursor` is absent.
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| limit | number | optional | Page size, 1-100. Default 50 |
| cursor | string | optional | nextCursor from the previous page |
Request
curl -H "x-api-key: YOUR_API_KEY" \
"https://api.nopaque.co.uk/testing/survey-results?limit=50"Response
{
"results": [
{
"runId": "9b2e4f6a-1c3d-4e5f-8a7b-6c5d4e3f2a1b",
"configId": "4c0f1b8e-6a7d-4e2b-9f0c-2d5a8b7e1c34",
"configName": "Unhappy after engineer visit - abandons",
"scenario": "abandons",
"profileName": "Sam Patel",
"agentE164": "+447457416494",
"endUserE164": "+447700900123",
"channel": "sms_chat",
"startedAt": "2026-11-02T10: 05: 00Z",
"expiresAt": 1793614200,
"expectedTurns": 4,
"answersGiven": 2,
"engagedAt": "2026-11-02T10: 06: 10Z",
"endedAt": "2026-11-02T10: 08: 40Z",
"state": "ended",
"outcome": "partial",
"endReason": "survey_over",
"turnsTruncated": false,
"capture": {
"status": "final",
"capturedAt": "2026-11-02T10: 09: 00Z"
}
}
],
"nextCursor": "eyJrIjoiLi4uIn0"
}Get a survey result
/testing/survey-results/{id}One test's result with its conversation: every message your platform sent (`from: survey`) and every answer the respondent gave (`from: respondent`), oldest first. While the test is live the conversation may be `provisional` and still grow. If it could not be retrieved, `capture.status` is `failed` and `capture.error` says why. For a few seconds after a test starts this may return 404 until the result is first written.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | required | Survey run ID (`runId`), as returned when the test was started |
Request
curl -H "x-api-key: YOUR_API_KEY" \
https://api.nopaque.co.uk/testing/survey-results/9b2e4f6a-1c3d-4e5f-8a7b-6c5d4e3f2a1bResponse
{
"runId": "9b2e4f6a-1c3d-4e5f-8a7b-6c5d4e3f2a1b",
"configId": "4c0f1b8e-6a7d-4e2b-9f0c-2d5a8b7e1c34",
"configName": "Unhappy after engineer visit - abandons",
"scenario": "abandons",
"profileName": "Sam Patel",
"agentE164": "+447457416494",
"endUserE164": "+447700900123",
"channel": "sms_chat",
"startedAt": "2026-11-02T10: 05: 00Z",
"expiresAt": 1793614200,
"expectedTurns": 4,
"answersGiven": 2,
"engagedAt": "2026-11-02T10: 06: 10Z",
"endedAt": "2026-11-02T10: 08: 40Z",
"state": "ended",
"outcome": "partial",
"endReason": "survey_over",
"turnsTruncated": false,
"capture": {
"status": "final",
"capturedAt": "2026-11-02T10: 09: 00Z"
},
"turns": [
{
"at": "2026-11-02T10: 06: 10Z",
"from": "survey",
"text": "Thanks for your recent engineer visit. On a scale of 1 to 5, how satisfied were you?"
},
{
"at": "2026-11-02T10: 06: 25Z",
"from": "respondent",
"text": "2"
},
{
"at": "2026-11-02T10: 06: 40Z",
"from": "survey",
"text": "Sorry to hear that. Did the engineer arrive on time? Reply Y or N."
},
{
"at": "2026-11-02T10: 07: 05Z",
"from": "respondent",
"text": "N"
}
]
}