# Documentación de la API — osTicket 1.17.x (Intervan)

Esta build incorpora una API HTTP extendida sobre la API estándar de osTicket.
Además del endpoint de creación de tickets, expone servicios para consultar,
responder, agregar mensajes, actualizar campos dinámicos, descargar adjuntos,
listar topics y ejecutar el cron.

> Enrutamiento definido en [api/http.php](../api/http.php). Controladores en
> [include/api.tickets.php](../include/api.tickets.php),
> [include/api.cron.php](../include/api.cron.php) y clase base en
> [include/class.api.php](../include/class.api.php).

---

## URL base

Todos los endpoints cuelgan de la carpeta `api/` de la instalación:

```
https://TU-DOMINIO/api/...
```

El [`.htaccess`](../api/.htaccess) reescribe internamente las peticiones hacia
`http.php`, de modo que **no** hace falta incluir `http.php` en la URL. Si el
`mod_rewrite` no estuviera disponible, las mismas rutas funcionan anteponiendo
`http.php`, por ejemplo `https://TU-DOMINIO/api/http.php/tickets/ticketInfo`.

---

## Autenticación

La autenticación es por **API key** más **IP de origen**. Toda petición debe
enviar el header:

```
X-API-Key: <TU_API_KEY>
```

Para que la clave sea aceptada (ver `requireApiKey()` en
[class.api.php](../include/class.api.php)):

1. La clave debe existir y estar **activa** (`isactive = 1`).
2. La **IP de origen** de la petición (`REMOTE_ADDR`) debe coincidir con la IP
   registrada para esa clave.

Además, algunas operaciones exigen permisos específicos de la clave:

| Permiso              | Requerido por                                    |
|----------------------|--------------------------------------------------|
| `can_create_tickets` | `POST /tickets`, `POST /tickets/reply.json`      |
| `can_exec_cron`      | `POST /tasks/cron`                               |

Las API keys se administran desde el panel de agentes:
**Admin Panel → Manage → API Keys**.

### Errores de autenticación
La mayoría de endpoints devuelven **HTTP 401** con un mensaje de texto cuando la
clave no es válida. Excepción: `GET /topics` devuelve **HTTP 500** con un cuerpo
JSON `{"status_code":"FAILURE", ...}` (comportamiento propio de esa ruta).

---

## Formato de respuesta

- **Creación de tickets** (`POST /tickets`): responde `201` con el **número de
  ticket** en texto plano (`text/html`).
- **Endpoints extendidos** (consultas, reply, message, etc.): responden
  `application/json` con la forma:

  ```json
  { "status_code": "0", "status_msg": "success", "...": "..." }
  ```

  En error devuelven HTTP `4xx/5xx` con `status_code: "FAILURE"` y el detalle en
  `status_msg`.

---

## Índice de endpoints

| Método | Ruta                              | Descripción                               | Auth extra           |
|--------|-----------------------------------|-------------------------------------------|----------------------|
| POST   | `/tickets.{xml\|json\|email}`     | Crear un ticket                           | `can_create_tickets` |
| POST   | `/tickets/reply.json`             | Responder un ticket (como agente)         | `can_create_tickets` |
| POST   | `/tickets/message.json`           | Agregar mensaje de cliente a un ticket    | —                    |
| POST   | `/tickets/postData.json`          | Actualizar campos dinámicos de un ticket  | —                    |
| GET    | `/tickets/downloadFile`           | Descargar un archivo adjunto              | —                    |
| GET    | `/tickets`                        | Listar tickets (resumen REST)             | —                    |
| GET    | `/tickets/ticketInfo`             | Detalle completo de un ticket             | —                    |
| GET    | `/tickets/staffTickets`           | Tickets asignados a un agente             | —                    |
| GET    | `/tickets/clientTickets`          | Tickets de un cliente (con filtros)       | —                    |
| GET    | `/topics.{xml\|json}`             | Listar help topics                        | —                    |
| POST   | `/tasks/cron`                     | Ejecutar el cron                          | `can_exec_cron`      |

---

## 1. Crear ticket — `POST /tickets.{xml|json|email}`

Crea un ticket nuevo. El formato de entrada se determina por la extensión de la
URL: `.json`, `.xml` o `.email` (mensaje RFC 822 crudo).

**Headers**
```
X-API-Key: <clave>
Content-Type: application/json   # o application/xml, o message/rfc822
```

**Body (JSON)** — campos habituales:

| Campo         | Tipo    | Notas                                                        |
|---------------|---------|--------------------------------------------------------------|
| `name`        | string  | Nombre del solicitante (obligatorio)                         |
| `email`       | string  | Email del solicitante (obligatorio)                          |
| `subject`     | string  | Asunto                                                       |
| `message`     | string  | Cuerpo. Admite formato RFC 2397 (`data:text/html,...`)       |
| `phone`       | string  | Teléfono                                                     |
| `topicId`     | int     | Help topic; habilita sus campos dinámicos                    |
| `department`  | string  | Nombre del departamento (se resuelve a `deptId`)             |
| `priorityId`  | int     | Prioridad                                                    |
| `source`      | string  | Origen (por defecto `API`)                                   |
| `ip`          | string  | IP del solicitante                                           |
| `alert`       | bool    | Enviar alertas a los agentes (default `true`)                |
| `autorespond` | bool    | Enviar autorespuesta al cliente (default `true`)             |
| `attachments` | array   | Adjuntos (ver abajo)                                         |
| `dynamicFields` | array | Campos del formulario del topic                              |

Los campos válidos exactos dependen de los formularios configurados (ticket
form, user form y forms del help topic). Ver `getRequestStructure()` en
[api.tickets.php](../include/api.tickets.php).

**Adjuntos** (base64):
```json
"attachments": [
  { "name": "captura.png", "type": "image/png", "encoding": "base64", "data": "iVBORw0KGgo..." }
]
```

**Ejemplo**
```bash
curl https://TU-DOMINIO/api/tickets.json \
  -H "X-API-Key: A1B2C3..." \
  -H "Content-Type: application/json" \
  -d '{
        "name": "Juan Pérez",
        "email": "juan@example.com",
        "subject": "No puedo iniciar sesión",
        "message": "data:text/html,No puedo acceder al portal.",
        "topicId": 1,
        "alert": true,
        "autorespond": true
      }'
```

**Respuesta** — `201 Created`, cuerpo = número de ticket (ej. `123456`).

---

## 2. Responder ticket — `POST /tickets/reply.json`

Publica una **respuesta de agente** en un ticket existente. Requiere permiso
`can_create_tickets`.

**Body**

| Campo           | Tipo   | Notas                                                      |
|-----------------|--------|------------------------------------------------------------|
| `ticketNumber`  | string | Número del ticket (obligatorio)                            |
| `staffUserName` | string | Username del agente que responde (obligatorio)             |
| `response`      | string | Texto de la respuesta                                      |
| `reply_status`  | string | (Opcional) Nombre del estado a aplicar (ej. `Closed`)      |

**Ejemplo**
```bash
curl https://TU-DOMINIO/api/tickets/reply.json \
  -H "X-API-Key: A1B2C3..." \
  -H "Content-Type: application/json" \
  -d '{
        "ticketNumber": "123456",
        "staffUserName": "agente1",
        "response": "Ya restablecimos tu contraseña.",
        "reply_status": "Closed"
      }'
```

**Respuesta** — `200`, `{"status_code":"0","status_msg":"reply posted successfully"}`.
`404` si el ticket no existe.

---

## 3. Agregar mensaje del cliente — `POST /tickets/message.json`

Publica un **mensaje del cliente** (como si lo hubiera escrito el usuario) en un
ticket existente.

**Body**

| Campo          | Tipo   | Notas                                            |
|----------------|--------|--------------------------------------------------|
| `ticketNumber` | string | Número del ticket (obligatorio)                  |
| `email`        | string | Email del usuario (se resuelve al `userId`)      |
| `message`      | string | Cuerpo del mensaje                               |

**Respuesta** — `200`, `{"status_code":"0","status_msg":"message posted successfully"}`.

---

## 4. Actualizar campos dinámicos — `POST /tickets/postData.json`

Actualiza valores de campos del formulario dinámico de un ticket.

**Body**
```json
{
  "ticketNumber": "123456",
  "dynamicFields": [
    { "fieldName": "codigo_cliente", "fieldData": "CLI-0099" },
    { "fieldName": "sucursal",       "fieldData": "Centro" }
  ]
}
```

Solo se actualizan los campos que ya existen en el formulario del ticket.

**Respuesta** — `200`, `{"status_code":"0","status_msg":"data posted successfully"}`.

---

## 5. Descargar adjunto — `GET /tickets/downloadFile`

Descarga el contenido binario de un archivo adjunto por su ID.

**Query params**

| Param    | Tipo | Notas                        |
|----------|------|------------------------------|
| `fileId` | int  | ID del archivo (obligatorio) |

**Ejemplo**
```bash
curl -OJ "https://TU-DOMINIO/api/tickets/downloadFile?fileId=42" \
  -H "X-API-Key: A1B2C3..."
```

Responde con el stream del archivo. `404` si el `fileId` es inválido o no existe.
Los IDs de archivo se obtienen en `ticketInfo` (campo `files[].id`).

---

## 6. Listar tickets — `GET /tickets`

Devuelve un listado resumido de todos los tickets.

**Respuesta** — array de tickets; cada ticket es una lista de pares
`[campo, valor]` con `number`, `created`, `updated`, `closed` y un `href`:

```json
[
  [["number","123456"],["created","2026-08-01 10:00:00"],
   ["updated","2026-08-02 09:00:00"],["closed",null],
   ["href","/api/tickets/123456"]]
]
```

---

## 7. Detalle de un ticket — `GET /tickets/ticketInfo`

Devuelve el detalle completo de un ticket, incluyendo el hilo de mensajes,
campos dinámicos y adjuntos.

**Query params**

| Param          | Tipo   | Notas                            |
|----------------|--------|----------------------------------|
| `ticketNumber` | string | Número del ticket (obligatorio)  |

**Respuesta** (resumida)
```json
{
  "status_code": "0",
  "status_msg": "ticket details retrieved successfully",
  "ticket": {
    "number": "123456",
    "topic_id": 1,
    "dynamicFields": { "codigo_cliente": "CLI-0099" },
    "thread_entries": [
      {
        "id": 987,
        "files": [
          { "id": 42, "mime_type": "image/png", "name": "captura.png" }
        ]
      }
    ]
  }
}
```

Códigos: `404` si el ticket no existe, `422` si falta `ticketNumber`.

---

## 8. Tickets de un agente — `GET /tickets/staffTickets`

Lista los tickets **asignados** a un agente.

**Query params**

| Param           | Tipo   | Notas                             |
|-----------------|--------|-----------------------------------|
| `staffUserName` | string | Username del agente (obligatorio) |

**Respuesta** — `{ "tickets": [ ... ], "status_code": "0", "status_msg": "success" }`.

---

## 9. Tickets de un cliente — `GET /tickets/clientTickets`

Lista los tickets de un cliente (por email) con **filtrado dinámico** opcional.

**Query params**

| Param             | Tipo   | Notas                                    |
|-------------------|--------|------------------------------------------|
| `clientUserMail`  | string | Email del cliente (obligatorio)          |
| *(filtros)*       | varios | Ver más abajo                            |

Códigos: `404` si el usuario no existe, `422` si falta `clientUserMail`.

### Filtrado dinámico

Solo se pueden filtrar estos campos:

| Parámetro    | Tipo   |
|--------------|--------|
| `state`      | string |
| `status`     | string |
| `statusId`   | int    |
| `created`    | date   |
| `lastupdate` | date   |
| `closed`     | date   |
| `number`     | string |
| `topicId`    | int    |
| `deptId`     | int    |
| `staffId`    | int    |
| `teamId`     | int    |
| `source`     | string |

Operadores disponibles: `exact` (default), `gt`, `gte`, `lt`, `lte`, `in`,
`range`, `contains`, `startswith`, `endswith`, `isnull`.

**Modo 1 — parámetros planos (AND implícito).** El operador se indica con el
sufijo `__op`:

```
GET /api/tickets/clientTickets?clientUserMail=juan@example.com&state=open&created__gte=2026-07-01&topicId__in=1,2
```

**Modo 2 — árbol JSON** (soporta `any`/OR, `all`/AND, `not`). Se pasa en el
parámetro `filter` (tiene prioridad sobre el modo plano):

```
GET /api/tickets/clientTickets?clientUserMail=juan@example.com&filter={
  "any": [
    { "state": "open" },
    { "all": [ { "created": { "gte": "2026-07-01" } }, { "state": "closed" } ] }
  ]
}
```

> El valor de `filter` debe ir URL-encodeado. Un campo u operador no permitido
> devuelve error `FAILURE` con el detalle en `status_msg`.

**Respuesta** — `{ "tickets": [ ... ], "status_code": "0", "status_msg": "success" }`.

---

## 10. Listar help topics — `GET /topics.{xml|json}`

Devuelve los help topics. Si las *Internal Notes* del topic contienen un bloque
JSON `{...}`, se extrae en el campo `parameters` y se quita del texto `notes`.

**Respuesta**
```json
{
  "status_code": "0",
  "status_msg": "success",
  "topics": [
    {
      "topicId": 1,
      "topicPId": 0,
      "topic": "Soporte / General",
      "parameters": { "sla": "24h" },
      "notes": "Notas del topic",
      "active": "S"
    }
  ]
}
```

`active` es `"S"` si el topic está activo, `"N"` si no.

> ⚠️ Si la clave no está autorizada, esta ruta responde **HTTP 500** (no 401)
> con `status_code: "FAILURE"`.

---

## 11. Ejecutar cron — `POST /tasks/cron`

Dispara la ejecución de las tareas programadas de osTicket. Requiere permiso
`can_exec_cron` en la API key.

**Ejemplo**
```bash
curl -X POST https://TU-DOMINIO/api/tasks/cron -H "X-API-Key: A1B2C3..."
```

**Respuesta** — `200`, cuerpo `Completed`.

---

## Códigos de estado usados

| HTTP | Significado                                                         |
|------|--------------------------------------------------------------------|
| 200  | OK (endpoints de consulta/acción)                                  |
| 201  | Ticket creado                                                      |
| 400  | Datos inesperados o cuerpo ilegible                                |
| 401  | API key inválida / IP no autorizada / sin permiso                  |
| 404  | Recurso no encontrado (ticket, usuario, archivo)                   |
| 415  | Formato de datos no soportado                                      |
| 422  | Falta un parámetro obligatorio                                     |
| 500  | Error interno (los endpoints extendidos devuelven `FAILURE` JSON)  |

---

## Notas de seguridad

- La API key viaja en el header `X-API-Key`; usá siempre **HTTPS**.
- El acceso está atado a la **IP de origen** registrada para cada clave.
- No versiones la clave en el código cliente; guardala en configuración/secretos.
