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

# POST /v1/crm/tickets

> Open a ticket for a contact. Company and project come from the token; Keebai generates the id.

Creates a ticket — a case, deal, or conversation you want to track through a pipeline. The only mandatory field is `chat_user`: the contact the ticket belongs to.

The `company` and `project` are taken from your token and must **not** appear in the body. Sending them returns `400`.

<Tip>
  A ticket is anchored to a **chat user**, not to a [customer](/dev/endpoints/crm-customer-create). The chat user is the contact record Keebai creates when someone writes to one of your channels. Get its id from an [inbound message webhook](/dev/webhooks/events) or from the portal.
</Tip>

## Endpoint

```
POST https://api.keebai.com/v1/crm/tickets
```

## Required scope

`crm:tickets:write`

## Headers

| Header          | Required | Value                    |
| --------------- | -------- | ------------------------ |
| `Authorization` | Yes      | `Bearer kbai_pk_<token>` |
| `Content-Type`  | Yes      | `application/json`       |

## Body

| Field                  | Type     | Required | Description                                                     |
| ---------------------- | -------- | -------- | --------------------------------------------------------------- |
| `chat_user`            | `string` | Yes      | `ObjectId` of the contact the ticket belongs to.                |
| `chat_user_name`       | `string` | No       | Display name, denormalized onto the ticket.                     |
| `chat_user_avatar`     | `string` | No       | Avatar URL, denormalized onto the ticket.                       |
| `chat_user_channel`    | `string` | No       | Origin channel, e.g. `whatsapp`, `instagram`.                   |
| `chat_user_channel_id` | `string` | No       | `ObjectId` of the specific channel.                             |
| `stage`                | `string` | No       | `ObjectId` of the pipeline stage to open the ticket in.         |
| `priority`             | `string` | No       | One of `low`, `medium`, `high`, `urgent`. Defaults to `medium`. |
| `category`             | `string` | No       | Free-text category.                                             |
| `assigned_to`          | `string` | No       | `ObjectId` of the user who owns the ticket.                     |
| `custom_fields`        | `object` | No       | Free-form object matching your company's custom fields.         |
| `amount`               | `number` | No       | Monetary value of the opportunity.                              |
| `amount_currency`      | `string` | No       | ISO 4217 currency, e.g. `CLP`, `USD`.                           |
| `amount_status`        | `string` | No       | `estimated` or `confirmed`.                                     |

## Example request

<CodeGroup>
  ```bash curl theme={null}
  curl -X POST https://api.keebai.com/v1/crm/tickets \
    -H "Authorization: Bearer kbai_pk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
    -H "Content-Type: application/json" \
    -d '{
      "chat_user": "66c1d2e3f4a5b6c7d8e9f0a1",
      "chat_user_name": "Juan Pérez",
      "chat_user_channel": "whatsapp",
      "stage": "67d1e2f3a4b5c6d7e8f9a0b1",
      "priority": "high",
      "category": "soporte",
      "amount": 150000,
      "amount_currency": "CLP",
      "amount_status": "estimated"
    }'
  ```

  ```js JavaScript theme={null}
  const response = await fetch("https://api.keebai.com/v1/crm/tickets", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.KEEBAI_API_TOKEN}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      chat_user: chatUserId,
      chat_user_name: "Juan Pérez",
      chat_user_channel: "whatsapp",
      stage: stageId,
      priority: "high",
    }),
  });
  const { ticket } = await response.json();
  ```

  ```python Python theme={null}
  import os, requests

  resp = requests.post(
      "https://api.keebai.com/v1/crm/tickets",
      headers={"Authorization": f"Bearer {os.environ['KEEBAI_API_TOKEN']}"},
      json={
          "chat_user": chat_user_id,
          "chat_user_name": "Juan Pérez",
          "chat_user_channel": "whatsapp",
          "stage": stage_id,
          "priority": "high",
      },
      timeout=10,
  )
  resp.raise_for_status()
  ticket = resp.json()["ticket"]
  ```
</CodeGroup>

## Response

### 201 Created

```json theme={null}
{
  "ticket": {
    "_id": "65f4b5c6d7e8f9a0b1c2d3e4",
    "chat_user": "66c1d2e3f4a5b6c7d8e9f0a1",
    "chat_user_name": "Juan Pérez",
    "chat_user_channel": "whatsapp",
    "stage": "67d1e2f3a4b5c6d7e8f9a0b1",
    "is_closed": false,
    "priority": "high",
    "category": "soporte",
    "custom_fields": {},
    "amount": 150000,
    "amount_currency": "CLP",
    "amount_status": "estimated",
    "created_at": "2026-05-02T14:31:07.221Z",
    "updated_at": "2026-05-02T14:31:07.221Z"
  },
  "stage_requested": "67d1e2f3a4b5c6d7e8f9a0b1"
}
```

`stage_requested` echoes back the stage you asked for, and is present only when you sent one.

### 400 Bad Request

Missing `chat_user`, an id that is not a valid `ObjectId`, a `priority` outside the allowed set, or a property the endpoint does not accept — including `company` and `project`, which are derived from the token.

### 401 Unauthorized

Missing, invalid, revoked, or expired token.

### 403 Forbidden

The token does not have the `crm:tickets:write` scope.

## Operational notes

* **No stage is assigned by default.** If you omit `stage`, the ticket is created without one and will not appear in a pipeline board until you move it with [`POST /v1/crm/tickets/:id/stage`](/dev/endpoints/crm-ticket-stage).
* **Tickets open as `is_closed: false`.** Close one by sending `{"is_closed": true, "resolution": "won"}` to [`PATCH /v1/crm/tickets/:id`](/dev/endpoints/crm-ticket-update).
* **Nothing deduplicates by contact.** Two `POST`s for the same `chat_user` create two tickets. Check for an existing open ticket first if that is not what you want.


## OpenAPI

````yaml POST /v1/crm/tickets
openapi: 3.0.0
info:
  title: Keebai Public API
  description: Superficie pública REST autenticada con Personal Access Tokens (PAT).
  version: 1.0.0
  contact: {}
servers:
  - url: https://api.keebai.com
    description: Production
security: []
tags: []
paths:
  /v1/crm/tickets:
    post:
      tags:
        - crm-tickets
      summary: Crear un ticket
      description: >-
        La company y el project se toman del token; enviarlos en el body
        devuelve 400.
      operationId: PublicTicketsController_create
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PublicTicketCreateDto'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicTicketEnvelopeDto'
      security:
        - PAT: []
components:
  schemas:
    PublicTicketCreateDto:
      type: object
      properties:
        chat_user:
          type: string
          description: Id del chat user (contacto) dueño del ticket
        chat_user_name:
          type: string
        chat_user_avatar:
          type: string
        chat_user_channel:
          type: string
          description: Canal de origen (whatsapp, instagram…)
        chat_user_channel_id:
          type: string
        priority:
          type: string
          enum:
            - low
            - medium
            - high
            - urgent
          default: medium
        category:
          type: string
        assigned_to:
          type: string
          description: Id del usuario asignado
        custom_fields:
          type: object
        amount:
          type: number
          description: Valor monetario de la oportunidad
        amount_currency:
          type: string
          description: Moneda ISO 4217
        amount_status:
          type: string
          enum:
            - estimated
            - confirmed
        stage:
          type: string
          description: Etapa del pipeline. Si se omite, el ticket queda sin etapa asignada.
      required:
        - chat_user
    PublicTicketEnvelopeDto:
      type: object
      properties:
        ticket:
          $ref: '#/components/schemas/PublicTicketResponseDto'
      required:
        - ticket
    PublicTicketResponseDto:
      type: object
      properties:
        _id:
          type: string
          description: ObjectId del ticket
        chat_user:
          type: string
          description: ObjectId del chat user (contacto)
        chat_user_name:
          type: string
        chat_user_avatar:
          type: string
        chat_user_channel:
          type: string
          description: Canal de origen (whatsapp, instagram…)
        chat_user_channel_id:
          type: string
        stage:
          description: Etapa actual del pipeline. Ausente si el ticket no tiene una.
          allOf:
            - $ref: '#/components/schemas/PublicTicketStageDto'
        is_closed:
          type: boolean
        resolution:
          type: string
          enum:
            - won
            - lost
        priority:
          type: string
          enum:
            - low
            - medium
            - high
            - urgent
        category:
          type: string
        assigned_to:
          $ref: '#/components/schemas/PublicTicketAssigneeDto'
        tags:
          type: array
          items:
            $ref: '#/components/schemas/PublicTicketTagDto'
        custom_fields:
          type: object
        amount:
          type: number
        amount_currency:
          type: string
          description: Moneda ISO 4217
        amount_status:
          type: string
          enum:
            - estimated
            - confirmed
        conversation_summary:
          $ref: '#/components/schemas/PublicConversationSummaryDto'
        ai_feedback_status:
          type: string
          description: 'Feedback de la IA: resolved | unresolved'
        appointment_ids:
          description: Citas de scheduling vinculadas al ticket
          type: array
          items:
            type: string
        last_message:
          description: Último mensaje del contacto. Sólo presente en el listado.
          allOf:
            - $ref: '#/components/schemas/PublicTicketLastMessageDto'
        closed_at:
          type: string
          format: date-time
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      required:
        - _id
        - chat_user
        - is_closed
        - priority
        - tags
        - custom_fields
        - created_at
        - updated_at
    PublicTicketStageDto:
      type: object
      properties:
        stage_id:
          type: string
          description: Etapa de destino dentro del pipeline
        prioridad:
          type: string
          description: Prioridad reportada al mover la etapa
        contexto:
          type: string
          description: Contexto del movimiento
        problema:
          type: string
          description: Problema detectado
        recomendaciones:
          type: string
          description: Recomendaciones para el operador
      required:
        - stage_id
    PublicTicketAssigneeDto:
      type: object
      properties:
        id:
          type: string
          description: ObjectId del usuario
        full_name:
          type: string
        email:
          type: string
      required:
        - id
    PublicTicketTagDto:
      type: object
      properties:
        id:
          type: string
          description: ObjectId del tag
        name:
          type: string
        color:
          type: string
          description: 'Color hex, ej. #1890ff'
      required:
        - id
        - name
    PublicConversationSummaryDto:
      type: object
      properties:
        motivo:
          type: string
        estado:
          type: string
        proximos_pasos:
          type: string
        is_final:
          type: boolean
    PublicTicketLastMessageDto:
      type: object
      properties:
        id:
          type: string
        content:
          type: string
        role:
          type: string
        direction:
          type: string
          description: inbound | outbound
        created_at:
          type: string
          format: date-time
      required:
        - id
  securitySchemes:
    PAT:
      scheme: bearer
      bearerFormat: kbai_pk_<hex>
      type: http
      description: >-
        Personal Access Token con prefijo `kbai_pk_`. Generar desde el portal
        con permiso `developer.manage_tokens`.

````