# Direct Survey

`POST /api/v1/surveys/direct-order`

Start a voice survey without a pre-built template. You send the survey tree in the request: optional start voices, the question, what each keypad option does (play voices or transfer to an agent), and optional end voices. Every voice slot takes either an approved voice from your [voice library](/api-docs/voices/list) or a CDN audio URL.

:::caution[Permission required for audio URLs]
Approved voice-library references (`{"type": "voice", "name": "..."}`) work on every account with survey access. Plain audio URLs skip voice approval, so they need extra permission on your account — contact support to enable it. A request that contains any URL entry from an account without it is rejected with `403`, listing each URL field.
:::

## Request Parameters

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| request_id | string | Yes | Unique request identifier (16-64 chars) to prevent duplicate requests. Use UUID or random string. |
| sender | string | Yes | Your active caller sender number |
| phone_numbers | array | Yes | Array of Bangladeshi phone numbers (01XXXXXXXXX format, 1-999 numbers). No duplicates allowed. |
| start_voices | array | No | Voice entries (0-10) played in sequence before the question |
| question_voices | array | Yes | Voice entries (1-10) for the question, played in sequence |
| invalid_voice | voice entry | No | Played when the caller presses a key that matches no option, before the question repeats. If omitted, the question repeats after silence. |
| end_voices | array | No | Voice entries (0-10) played at the end of the call |
| dtmf_options | array | Yes | Keypad options (1-9 items) |
| dtmf_options[].key | string | Yes | Keypad key, `1`-`9` (`0`, `*`, `#` not allowed). Keys must be unique. |
| dtmf_options[].option_type | string | No | `voice` (default) or `transfer` |
| dtmf_options[].voices | array | No | Voice entries (0-10) played after the caller picks this option |
| dtmf_options[].transfer_numbers | array | For `transfer` | Numbers to transfer the caller to |
| dtmf_options[].ringback_voice | voice entry | No | Transfer only: played while connecting |
| dtmf_options[].all_busy_voice | voice entry | No | Transfer only: played when every transfer number is busy |
| metadata | object | No | Custom data to associate with the survey. Returned in results and the webhook payload. |
| webhook_url | string | No | URL to receive a [webhook](/api-docs/surveys/webhooks) when the survey completes |
| config.retry_count | number | No | Retry attempts for failed calls, `0`-`3` (default `0`) |

## Voice entries

Every voice field accepts entries in either form, and you can mix them in the same array:

```jsonc
{ "type": "voice", "name": "welcome-intro" }  // approved voice from your voice library
"https://cdn.example.com/audio/part1.wav"     // CDN audio URL (needs extra permission)
```

**Voice-library references**

- `name` is the voice name shown in your dashboard voice library ([List Voices](/api-docs/voices/list) returns them). Matching ignores case.
- The voice must be approved, not archived, and fully static — voices with dynamic parts are rejected.
- A multi-part voice expands to all of its parts, played in order. The 1-10 limit on array fields counts your request entries, before expansion.
- In single-file fields (`invalid_voice`, `ringback_voice`, `all_busy_voice`) the voice must have exactly one part.

**Audio URLs**

- Must be WAV, 8000 Hz, mono, `pcm_s16le` (G.711-compatible). Every URL is downloaded and validated before the survey starts; invalid files are rejected with `400`.
- Require extra permission on your account (see above).

Any other entry type (for example `{"type": "tts"}`) is reserved and currently fails with `422`.

## Request Example

```bash
curl -X POST https://api.awajdigital.com/api/v1/surveys/direct-order \
  -H "Authorization: Bearer your_api_token_here" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "request_id": "unique_direct_survey_123",
    "sender": "8801234567890",
    "phone_numbers": ["019XXXXXXXX", "018XXXXXXXX"],
    "start_voices": [{ "type": "voice", "name": "welcome-intro" }],
    "question_voices": [{ "type": "voice", "name": "delivery-question" }],
    "invalid_voice": { "type": "voice", "name": "invalid-key" },
    "end_voices": [{ "type": "voice", "name": "thank-you" }],
    "dtmf_options": [
      { "key": "1", "voices": [{ "type": "voice", "name": "order-confirmed" }] },
      {
        "key": "2",
        "option_type": "transfer",
        "transfer_numbers": ["017XXXXXXXX"],
        "ringback_voice": { "type": "voice", "name": "please-wait" },
        "all_busy_voice": { "type": "voice", "name": "agents-busy" }
      }
    ],
    "metadata": { "campaign_id": "delivery_confirm_2026" },
    "webhook_url": "https://your-domain.com/webhook",
    "config": { "retry_count": 1 }
  }'
```

```javascript
// Browser JavaScript (Fetch API)
const response = await fetch('https://api.awajdigital.com/api/v1/surveys/direct-order', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer your_api_token_here',
    'Accept': 'application/json',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    request_id: 'unique_direct_survey_123',
    sender: '8801234567890',
    phone_numbers: ['019XXXXXXXX', '018XXXXXXXX'],
    start_voices: [{ type: 'voice', name: 'welcome-intro' }],
    question_voices: [{ type: 'voice', name: 'delivery-question' }],
    invalid_voice: { type: 'voice', name: 'invalid-key' },
    end_voices: [{ type: 'voice', name: 'thank-you' }],
    dtmf_options: [
      { key: '1', voices: [{ type: 'voice', name: 'order-confirmed' }] },
      {
        key: '2',
        option_type: 'transfer',
        transfer_numbers: ['017XXXXXXXX'],
        ringback_voice: { type: 'voice', name: 'please-wait' },
        all_busy_voice: { type: 'voice', name: 'agents-busy' }
      }
    ],
    metadata: { campaign_id: 'delivery_confirm_2026' },
    webhook_url: 'https://your-domain.com/webhook',
    config: { retry_count: 1 }
  })
});

const data = await response.json();
console.log(data);
```

```javascript
// Node.js with axios
const axios = require('axios');

async function createDirectSurvey() {
  try {
    const response = await axios.post('https://api.awajdigital.com/api/v1/surveys/direct-order', {
      request_id: 'unique_direct_survey_123',
      sender: '8801234567890',
      phone_numbers: ['019XXXXXXXX', '018XXXXXXXX'],
      start_voices: [{ type: 'voice', name: 'welcome-intro' }],
      question_voices: [{ type: 'voice', name: 'delivery-question' }],
      invalid_voice: { type: 'voice', name: 'invalid-key' },
      end_voices: [{ type: 'voice', name: 'thank-you' }],
      dtmf_options: [
        { key: '1', voices: [{ type: 'voice', name: 'order-confirmed' }] },
        {
          key: '2',
          option_type: 'transfer',
          transfer_numbers: ['017XXXXXXXX'],
          ringback_voice: { type: 'voice', name: 'please-wait' },
          all_busy_voice: { type: 'voice', name: 'agents-busy' }
        }
      ],
      metadata: { campaign_id: 'delivery_confirm_2026' },
      webhook_url: 'https://your-domain.com/webhook',
      config: { retry_count: 1 }
    }, {
      headers: {
        'Authorization': 'Bearer your_api_token_here',
        'Accept': 'application/json',
        'Content-Type': 'application/json'
      }
    });

    console.log(response.data);
  } catch (error) {
    console.error('Error:', error.response?.data || error.message);
  }
}

createDirectSurvey();
```

```python
# Python with requests
import requests

def create_direct_survey():
    url = 'https://api.awajdigital.com/api/v1/surveys/direct-order'
    headers = {
        'Authorization': 'Bearer your_api_token_here',
        'Accept': 'application/json',
        'Content-Type': 'application/json'
    }
    data = {
        'request_id': 'unique_direct_survey_123',
        'sender': '8801234567890',
        'phone_numbers': ['019XXXXXXXX', '018XXXXXXXX'],
        'start_voices': [{'type': 'voice', 'name': 'welcome-intro'}],
        'question_voices': [{'type': 'voice', 'name': 'delivery-question'}],
        'invalid_voice': {'type': 'voice', 'name': 'invalid-key'},
        'end_voices': [{'type': 'voice', 'name': 'thank-you'}],
        'dtmf_options': [
            {'key': '1', 'voices': [{'type': 'voice', 'name': 'order-confirmed'}]},
            {
                'key': '2',
                'option_type': 'transfer',
                'transfer_numbers': ['017XXXXXXXX'],
                'ringback_voice': {'type': 'voice', 'name': 'please-wait'},
                'all_busy_voice': {'type': 'voice', 'name': 'agents-busy'}
            }
        ],
        'metadata': {'campaign_id': 'delivery_confirm_2026'},
        'webhook_url': 'https://your-domain.com/webhook',
        'config': {'retry_count': 1}
    }

    try:
        response = requests.post(url, json=data, headers=headers)
        print(response.json())
    except requests.exceptions.RequestException as e:
        print(f'Error: {e}')

create_direct_survey()
```

```php
<?php
// PHP with cURL
$url = 'https://api.awajdigital.com/api/v1/surveys/direct-order';
$token = 'your_api_token_here';

$data = [
    'request_id' => 'unique_direct_survey_123',
    'sender' => '8801234567890',
    'phone_numbers' => ['019XXXXXXXX', '018XXXXXXXX'],
    'start_voices' => [['type' => 'voice', 'name' => 'welcome-intro']],
    'question_voices' => [['type' => 'voice', 'name' => 'delivery-question']],
    'invalid_voice' => ['type' => 'voice', 'name' => 'invalid-key'],
    'end_voices' => [['type' => 'voice', 'name' => 'thank-you']],
    'dtmf_options' => [
        ['key' => '1', 'voices' => [['type' => 'voice', 'name' => 'order-confirmed']]],
        [
            'key' => '2',
            'option_type' => 'transfer',
            'transfer_numbers' => ['017XXXXXXXX'],
            'ringback_voice' => ['type' => 'voice', 'name' => 'please-wait'],
            'all_busy_voice' => ['type' => 'voice', 'name' => 'agents-busy']
        ]
    ],
    'metadata' => ['campaign_id' => 'delivery_confirm_2026'],
    'webhook_url' => 'https://your-domain.com/webhook',
    'config' => ['retry_count' => 1]
];

$ch = curl_init($url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer ' . $token,
    'Accept: application/json',
    'Content-Type: application/json'
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

$response = curl_exec($ch);
curl_close($ch);

$result = json_decode($response, true);
print_r($result);
?>
```

With the URL permission enabled, any entry can be a CDN URL instead, mixed freely with library voices:

```json
"question_voices": [
  "https://cdn.example.com/audio/question_part1.wav",
  { "type": "voice", "name": "question-part-2" }
]
```

## Success Response Example

```json
{
  "success": true,
  "survey": {
    "id": 789,
    "name": "direct_order_v1_1_1705312800000",
    "status": "surveying",
    "totalCount": 2,
    "createdAt": "2026-09-25T14:30:00.000+06:00",
    "metadata": { "campaign_id": "delivery_confirm_2026" }
  }
}
```

Poll [Get Survey Result](/api-docs/surveys/result) for per-number responses, or set `webhook_url` to be notified on completion.

## Error Responses

| Status | Description |
| --- | --- |
| 400 | Duplicate phone numbers or DTMF keys; audio URL not in G.711-compatible format; voice not found / not approved / archived; voice has dynamic parts; multi-part voice in a single-file field |
| 402 | Insufficient account balance |
| 403 | Audio URL entries sent without the URL permission, or sender not found / not active |
| 409 | `request_id` already used within 15 minutes |
| 422 | Invalid request parameters (including `tts` or unknown voice entry types) |

Voice and audio problems are reported together in a single `400`, one item per field in `errors`:

```json
{
  "success": false,
  "message": "Audio file validation failed. Files must be in G.711-compatible format (WAV, 8000Hz, Mono, pcm_s16le)",
  "errors": [
    { "field": "question_voices[1]", "issue": "voice \"greeting-v2\" not found or not approved" },
    { "field": "dtmf_options[0].voices[0]", "issue": "voice \"promo\" has dynamic parts; only fully static voices are allowed" }
  ]
}
```

URL entries without the permission return `403` in the same shape:

```json
{
  "success": false,
  "message": "Direct audio URLs are not enabled for your account. Use approved voices ({\"type\": \"voice\", \"name\": \"...\"}) or contact support.",
  "errors": [
    { "field": "question_voices[0]", "issue": "direct audio URLs require the send-direct-survey permission" }
  ]
}
```

:::note
Use a unique `request_id` for each request. The same `request_id` within 15 minutes is rejected with `409`. A request rejected with `403` for URL entries is not recorded, so you can retry it with the same `request_id`.
:::