# Support

Open a ticket with Vocatech support from your own system.

## Open a support ticket

`POST /v1/support/tickets`

Opens a ticket with Vocatech support from your own system: a help desk sync, an automation, or your AI agent's "after a message" action. The ticket belongs to the account your key belongs to and is worked like any email or text to support.

**One matter, one ticket.** Send the same `reference` again within 7 days and your message is added to the ticket it opened (`200`, `appended: true`) instead of opening a new one. A `reference` that is a ticket key you were given reports back on that ticket.

**Busy.** `409` means that ticket is being worked right now. Retry after `Retry-After` seconds (60).

Send contact details flat (`contact_name`, `contact_phone`, `contact_email`, `contact_company`, `contact_company_phone`, `reply_by`) or in a `contact` object (`name`, `phone`, `email`, `company`, `company_phone`, `reply_by`). The flat field wins when both are sent.

**Whose account.** Support finds the customer's account from the business phone number first (`contact_company_phone`), then `contact_phone`, then `caller_id`. Send the business name and number when you have them.

Keys with the `support` scope can call this route.

### Request

**curl**

```bash
curl -X POST https://api.vocatech.com/v1/support/tickets \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "message": "Our fax line has been down since this morning. Please call me back.",
    "contact_name": "Jordan Reyes",
    "contact_phone": "7185550142",
    "reply_by": "call",
    "reference": "ticket-sync-2291",
    "source": "Help desk sync"
  }'
```

**Node**

```javascript
const res = await fetch("https://api.vocatech.com/v1/support/tickets", {
  method: "POST",
  headers: {
    Authorization: "Bearer YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    message: "Our fax line has been down since this morning. Please call me back.",
    contact_name: "Jordan Reyes",
    contact_phone: "7185550142",
    reply_by: "call",
    reference: "ticket-sync-2291",
    source: "Help desk sync",
  }),
});

console.log(res.status, await res.json());
```

**Python**

```python
import requests

r = requests.post(
    "https://api.vocatech.com/v1/support/tickets",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
    json={
        "message": "Our fax line has been down since this morning. Please call me back.",
        "contact_name": "Jordan Reyes",
        "contact_phone": "7185550142",
        "reply_by": "call",
        "reference": "ticket-sync-2291",
        "source": "Help desk sync",
    },
    timeout=30,
)
print(r.status_code, r.json())
```

**PHP**

```php
<?php
require 'vendor/autoload.php';

$client = new GuzzleHttp\Client([
    'base_uri' => 'https://api.vocatech.com',
    'headers' => ['Authorization' => 'Bearer YOUR_API_KEY'],
    'http_errors' => false,
]);

$res = $client->post('/v1/support/tickets', [
    'json' => [
        'message' => 'Our fax line has been down since this morning. Please call me back.',
        'contact_name' => 'Jordan Reyes',
        'contact_phone' => '7185550142',
        'reply_by' => 'call',
        'reference' => 'ticket-sync-2291',
        'source' => 'Help desk sync',
    ],
]);

echo $res->getStatusCode(), PHP_EOL, $res->getBody(), PHP_EOL;
```

### Body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `message` | string | yes | What the person needs, in their own words. Up to 4,000 characters. Up to 4,000 characters. |
| `subject` | string |  | One line. Cut to 120 characters. |
| `contact_name` | string |  | Cut to 120 characters. |
| `contact_phone` | string |  | A 10-digit US number. Punctuation and a leading 1 are allowed. |
| `contact_email` | string (email) |  |  |
| `contact_company` | string |  | The business the person is calling about, as they gave it. Cut to 120 characters. |
| `contact_company_phone` | string |  | The business's main phone number, as they gave it. Support matches the account on it first. A 10-digit US number, punctuation and a leading 1 allowed; anything else is left out and the ticket still opens. |
| `reply_by` | string |  | How the person wants to hear back. One of `call`, `text` or `email`. |
| `caller_id` | string |  | The number the person called from, when it differs from `contact_phone`. A 10-digit US number is kept; anything else (an extension, a withheld or an international number) is left out and the ticket still opens. |
| `reference` | string |  | Your own id for this matter. The same `reference` within 7 days adds to the same ticket. A ticket key reports back on that ticket. Matches `^[A-Za-z0-9._:-]{1,80}$`. |
| `source` | string |  | The system that opened it, for example `AI front desk` or `Help desk sync`. Cut to 80 characters. |
| `contact` | object |  | Another way to send the contact fields. A flat field wins when both are sent. |
| `contact.name` | string |  |  |
| `contact.phone` | string |  |  |
| `contact.email` | string (email) |  |  |
| `contact.company` | string |  |  |
| `contact.company_phone` | string |  |  |
| `contact.reply_by` | string |  | One of `call`, `text` or `email`. |

**Example**

```json
{
  "message": "Our fax line has been down since this morning. Please call me back.",
  "contact_name": "Jordan Reyes",
  "contact_phone": "7185550142",
  "contact_company": "Reyes Family Dental",
  "contact_company_phone": "7185550100",
  "reply_by": "call",
  "reference": "ticket-sync-2291",
  "source": "Help desk sync"
}
```

### Answers

#### 200

Added to the ticket the same `reference` opened.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `ticket` | object | yes |  |
| `ticket.key` | string | yes | The ticket's key. Send it as `reference` later to report back on this ticket. |
| `ticket.subject` | string | yes |  |
| `ticket.status` | string | yes | For example `open`. |
| `ticket.created_at` | string | yes | When the ticket was opened, ISO 8601. |
| `ticket.appended` | boolean | yes | True when your message was added to a ticket the same `reference` opened. |

**Example**

```json
{
  "ticket": {
    "key": "case-20260924-141502-3f9a1c7e5b2d4a6c8e0f1a3b5c7d9e2f",
    "subject": "Message from Jordan Reyes",
    "status": "open",
    "created_at": "2026-09-24T14:15:02+00:00",
    "appended": true
  }
}
```

#### 201

A new ticket was opened.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `ticket` | object | yes |  |
| `ticket.key` | string | yes | The ticket's key. Send it as `reference` later to report back on this ticket. |
| `ticket.subject` | string | yes |  |
| `ticket.status` | string | yes | For example `open`. |
| `ticket.created_at` | string | yes | When the ticket was opened, ISO 8601. |
| `ticket.appended` | boolean | yes | True when your message was added to a ticket the same `reference` opened. |

**Example**

```json
{
  "ticket": {
    "key": "case-20260924-141502-3f9a1c7e5b2d4a6c8e0f1a3b5c7d9e2f",
    "subject": "Message from Jordan Reyes",
    "status": "open",
    "created_at": "2026-09-24T14:15:02+00:00",
    "appended": false
  }
}
```

#### 401

No key, or a key that is wrong or turned off.

#### 403

Refused. The key is scoped away from this route, the request came from an address not on the key's allow-list, or the route refused the request (the message says why).

#### 409

That ticket is being worked right now. Retry after `Retry-After` seconds.

**Example**

```json
{
  "error": {
    "code": 409,
    "type": "CONFLICT",
    "message": "busy"
  }
}
```

#### 422

A field is missing or malformed. The message says which.

**Example**

```json
{
  "error": {
    "code": 422,
    "type": "UNPROCESSABLE_ENTITY",
    "message": "message is required: what the person needs, in their own words."
  }
}
```

**Example**

```json
{
  "error": {
    "code": 422,
    "type": "UNPROCESSABLE_ENTITY",
    "message": "reply_by must be call, text or email."
  }
}
```

**Example**

```json
{
  "error": {
    "code": 422,
    "type": "UNPROCESSABLE_ENTITY",
    "message": "contact_phone must be a 10 digit US number."
  }
}
```

**Example**

```json
{
  "error": {
    "code": 422,
    "type": "UNPROCESSABLE_ENTITY",
    "message": "reference may use letters, digits, dot, underscore, colon and dash, up to 80 characters."
  }
}
```

#### 429

The `tickets` counter is full (10 a minute, 200 a day per key), or another request limit was reached. Wait `Retry-After` seconds.

**Example**

```json
{
  "error": "Rate limit exceeded",
  "period": "minute",
  "limit": 10,
  "counter": "tickets",
  "retry_after": 42
}
```

#### 502

The support desk could not take the ticket right now. Try again in a minute.

**Example**

```json
{
  "error": {
    "code": 502,
    "type": "ERROR",
    "message": "The support desk could not take the ticket right now. Try again in a minute."
  }
}
```

#### 503

The support desk is not available right now, or the key could not be checked.

**Example**

```json
{
  "error": {
    "code": 503,
    "type": "ERROR",
    "message": "The support desk is not available right now."
  }
}
```
