> ## Documentation Index
> Fetch the complete documentation index at: https://docs.armin.cx/llms.txt
> Use this file to discover all available pages before exploring further.

# Create a ticket

> Create support tickets via POST /public/v1/tickets.

Create tickets programmatically from CRMs, order systems, custom backends, or automation tools (Zapier, Make, webhooks). Requires the **`tickets:write`** scope. Rate limit: **30 requests / minute** (see [Rate limits](/api/rate-limits)).

<Warning>
  Do not use legacy `POST https://api.armin.cx/public/tickets` for new integrations. Prefer v1 and see [Migrating from legacy](/api/v1/migration) for the sunset date (**2026-10-31**).
</Warning>

## Prerequisites

1. **API key** with **`tickets:read`** and **`tickets:write`** — create or update under [Settings → API](https://armin.cx/app/_/settings/api).
2. **Connected channel** — the `channel` value you send must match a channel type connected in your workspace (for example `email` or `widget`). If the organization has multiple channels of the same type, use `channel_identifier` to target a specific inbox address or identifier.
3. **HTTP client** — `curl`, Postman, or any server that can send `POST` with JSON.

## Request

|             |                                                              |
| ----------- | ------------------------------------------------------------ |
| **Method**  | `POST`                                                       |
| **URL**     | `https://api.armin.cx/public/v1/tickets`                     |
| **Headers** | `cx-api-key: YOUR_API_KEY`, `Content-Type: application/json` |

Send only the fields listed below. Extra fields in the JSON body return an error.

## Request body

### Required

| Field     | Type   | Description                                                                                                                                                                                                 |
| --------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `channel` | string | Primary inbox channel for the ticket. One of: `email`, `widget`, `whatsapp`, `voice`, `instagram`, `facebook`, `tiktok`, `youtube`, `contact_form`. The organization must have that channel type connected. |

### Optional — ticket

| Field                | Type      | Default  | Description                                                                                                                                                                                    |
| -------------------- | --------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `subject`            | string    | —        | Ticket title (max 500 characters).                                                                                                                                                             |
| `note`               | string    | —        | Internal note on the ticket (max 10,000 characters). Used when you omit `messages` or send an empty `messages` array.                                                                          |
| `tags`               | string\[] | —        | Tag names (max 20 tags, 100 characters each). Tags are find-or-create and attached to the new ticket.                                                                                          |
| `metadata`           | object    | —        | Key/value pairs for external references (max 50 keys; string values max 2,000 characters). Use for order IDs, external system IDs, or integration context — not shown to customers by default. |
| `channel_identifier` | string    | —        | Unique inbox identifier when you have multiple channels of the same `channel` type (for example a specific support email address).                                                             |
| `priority`           | string    | `medium` | `none`, `urgent`, `high`, `medium`, or `low`.                                                                                                                                                  |
| `status`             | string    | `open`   | `open`, `need_more_info`, `resolved`, or `closed`.                                                                                                                                             |
| `language`           | string    | —        | Ticket language (supported values match the inbox language list).                                                                                                                              |
| `is_spam`            | boolean   | `false`  | Mark the ticket as spam.                                                                                                                                                                       |
| `ticket_type`        | string    | —        | `regular` or `side`.                                                                                                                                                                           |
| `parent_ticket_id`   | UUID      | —        | Link a side ticket to a parent ticket.                                                                                                                                                         |
| `assignee_email`     | string    | —        | Assign the ticket to an agent by email.                                                                                                                                                        |

### Optional — contact

| Field                | Type   | Description                                                 |
| -------------------- | ------ | ----------------------------------------------------------- |
| `contact.email`      | string | Customer email. Matches an existing contact or creates one. |
| `contact.first_name` | string | Contact first name.                                         |
| `contact.last_name`  | string | Contact last name.                                          |
| `contact.phone`      | string | Contact phone number.                                       |

### Optional — messages

Instead of (or in addition to) `note`, you can send up to **10** initial messages in `messages[]`:

| Field                        | Type    | Description                                                                                                                                                                                      |
| ---------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `messages[].channel`         | string  | Channel for this message (same enum as ticket `channel`).                                                                                                                                        |
| `messages[].from_agent`      | boolean | `false` if omitted. Set to `true` to record the message as sent by your business instead of the customer. `from` is optional for agent messages.                                                 |
| `messages[].body`            | string  | Plain-text message content (max 100,000 characters).                                                                                                                                             |
| `messages[].html_body`       | string  | HTML message content (max 100,000 characters).                                                                                                                                                   |
| `messages[].prefilled_input` | boolean | `false` if omitted. Set to `true` to add `body` as a draft AI suggestion for the agent instead of a message in the thread. Only one message in the request can set this.                         |
| `messages[].from.email`      | string  | Sender email for customer messages. Optional if ticket-level `contact.email` is set.                                                                                                             |
| `messages[].from.first_name` | string  | Sender first name.                                                                                                                                                                               |
| `messages[].from.last_name`  | string  | Sender last name.                                                                                                                                                                                |
| `messages[].from.phone`      | string  | Sender phone.                                                                                                                                                                                    |
| `messages[].skip_email`      | boolean | `false` if omitted. Set to `true` to record the message without sending an outbound notification (email, etc.) — for example when backfilling or importing messages already delivered elsewhere. |
| `messages[].metadata`        | object  | Per-message metadata (same limits as ticket `metadata`).                                                                                                                                         |
| `messages[].attachment_ids`  | UUID\[] | IDs of attachments confirmed via the [Attachments](#attachments) flow below (max **10** per message). Not supported on `prefilled_input` messages.                                               |

<Tip>
  For most integrations, **`note` + `contact` + `metadata`** is enough. Use `messages` when you need a visible customer message in the thread when the ticket is created.
</Tip>

## Attachments

Attach a file to a message in three calls: request an upload URL, upload the file, then confirm it before referencing it on ticket creation.

1. `POST /public/v1/attachments/upload-url` with the file's name, MIME type, and size. Returns `id` and a presigned `upload_url` (valid for 30 minutes).
2. `PUT` the raw file bytes to `upload_url` — directly to storage, not through this API.
3. `POST /public/v1/attachments/{id}/confirm`. This checks the upload against the size limit and the file's **actual content**, not just the MIME type you declared in step 1.
4. Pass the confirmed `id` in `messages[].attachment_ids` when you create the ticket.

| Limit                       | Value                                                                   |
| --------------------------- | ----------------------------------------------------------------------- |
| Max file size               | 20 MB                                                                   |
| Max attachments per message | 10                                                                      |
| Allowed types               | Images, PDF, plain text, CSV, Word (`.docx`), Excel (`.xlsx`), MP4, MP3 |

```bash theme={null}
# 1. Get an upload URL
curl -sS -X POST \
  -H "cx-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  "https://api.armin.cx/public/v1/attachments/upload-url" \
  -d '{
    "file_name": "invoice.pdf",
    "mime_type": "application/pdf",
    "size": 20480
  }'
# -> { "data": { "id": "...", "upload_url": "...", "public_url": "...", "expires_at": "..." } }

# 2. Upload the file directly to the returned upload_url
curl -sS -X PUT "UPLOAD_URL_FROM_STEP_1" \
  -H "Content-Type: application/pdf" \
  --data-binary @invoice.pdf

# 3. Confirm the upload
curl -sS -X POST \
  -H "cx-api-key: YOUR_API_KEY" \
  "https://api.armin.cx/public/v1/attachments/ATTACHMENT_ID/confirm"

# 4. Create the ticket, referencing the confirmed attachment
curl -sS -X POST \
  -H "cx-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  "https://api.armin.cx/public/v1/tickets" \
  -d '{
    "channel": "email",
    "messages": [
      {
        "channel": "email",
        "from_agent": true,
        "body": "Here is your invoice.",
        "attachment_ids": ["ATTACHMENT_ID"]
      }
    ]
  }'
```

## Minimal example

Internal note only — no contact required:

```bash theme={null}
curl -sS -X POST \
  -H "cx-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  "https://api.armin.cx/public/v1/tickets" \
  -d '{
    "channel": "email",
    "subject": "Follow-up required",
    "note": "Created from order monitoring — customer missing phone number."
  }'
```

## Full example

Contact, tags, and external reference in `metadata`:

```bash theme={null}
curl -sS -X POST \
  -H "cx-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  "https://api.armin.cx/public/v1/tickets" \
  -d '{
    "channel": "email",
    "subject": "Missing phone number — Order #1042",
    "contact": {
      "email": "customer@example.com",
      "first_name": "Alex",
      "last_name": "Example"
    },
    "note": "Order #1042: shipping method requires a phone number but none was provided.\nCustomer: Alex Example\nPlease request a callback number before dispatch.",
    "tags": ["missing-phone", "express-shipping"],
    "metadata": {
      "external_order_id": "gid://shopify/Order/5678901234",
      "external_order_name": "#1042",
      "source": "order-automation"
    }
  }'
```

Replace `metadata` keys with whatever your system uses — the API stores them as opaque integration fields.

## Automation and webhooks

Many teams call this endpoint from **HTTP request** actions in automation platforms (custom scripts, Zapier, Make, n8n, ecommerce workflows, etc.):

1. Trigger when your business rule fires (tag added, field missing, status change).
2. `POST` to `https://api.armin.cx/public/v1/tickets` with your platform's variable syntax in `subject`, `note`, `contact`, and `metadata`.
3. Map your external order or case ID into `metadata` so agents can trace the source.

Design triggers so they fire **only when the condition becomes true**, not on every unrelated update.

<Note>
  Every successful request creates a **new** ticket. Run your automation only when the event should open a ticket (for example when a tag is added), not on every update to the same record.
</Note>

## Response

**`201 Created`** — standard v1 success envelope:

```json theme={null}
{
  "success": true,
  "data": {
    "id": "019b0000-0000-7000-8000-000000000001",
    "ticket_number": 1042,
    "subject": "Missing phone number — Order #1042",
    "status": "open",
    "priority": "medium",
    "channel": "email",
    "created_at": "2026-08-25T10:00:00.000Z",
    "updated_at": "2026-08-25T10:00:00.000Z",
    "tags": ["missing-phone", "express-shipping"]
  }
}
```

Store `data.id` (UUID) or `data.ticket_number` (numeric inbox number) to correlate with later `GET` requests.

## Errors

| Situation                                                             | HTTP       | Code                                       |
| --------------------------------------------------------------------- | ---------- | ------------------------------------------ |
| Missing or invalid API key                                            | 401        | `API_KEY_REQUIRED`, `INVALID_API_KEY`      |
| Key lacks `tickets:write`                                             | 403        | `FORBIDDEN_SCOPE`                          |
| Invalid body, unknown field, or missing `channel`                     | 400        | `VALIDATION_ERROR`                         |
| No connected channel for `channel` / `channel_identifier`             | 400        | `VALIDATION_ERROR`                         |
| Attachment not found, unconfirmed, or already used on another message | 404 or 400 | `ATTACHMENT_NOT_FOUND`, `VALIDATION_ERROR` |
| Rate limit exceeded                                                   | 429        | `RATE_LIMIT_EXCEEDED`                      |

See [Errors](/api/errors) for the full catalog and `request_id` usage.

## Try it first

1. In [Settings → API](https://armin.cx/app/_/settings/api), create a key with **`tickets:write`**.
2. Send a test request using the examples above. Use a test subject or tag (for example `api-test`) so your team can spot it in the inbox.
3. Confirm the ticket appears with the expected channel, contact, tags, and metadata.

Revoke the test key when you are finished.

## Related

<CardGroup cols={2}>
  <Card title="OpenAPI reference" icon="book" href="https://api.armin.cx/docs/v1">
    Interactive schema and Try it for `POST /tickets`.
  </Card>

  <Card title="Authentication" icon="key" href="/api/authentication">
    Scopes, key rotation, and least privilege.
  </Card>

  <Card title="Rate limits" icon="gauge" href="/api/rate-limits">
    Write tier (30/min) and backoff on 429.
  </Card>

  <Card title="Migrating from legacy" icon="arrow-right-arrow-left" href="/api/v1/migration">
    Map legacy `POST /public/tickets` to v1.
  </Card>
</CardGroup>
