Actualizar un ticket
curl --request PATCH \
--url https://api.keebai.com/v1/crm/tickets/{id} \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"is_closed": true,
"category": "<string>",
"assigned_to": "<string>",
"custom_fields": {},
"amount": 123,
"amount_currency": "<string>",
"tag_ids": [
"<string>"
],
"add_tag_ids": [
"<string>"
],
"remove_tag_ids": [
"<string>"
],
"conversation_summary": {
"motivo": "<string>",
"estado": "<string>",
"proximos_pasos": "<string>",
"is_final": true
}
}
'import requests
url = "https://api.keebai.com/v1/crm/tickets/{id}"
payload = {
"is_closed": True,
"category": "<string>",
"assigned_to": "<string>",
"custom_fields": {},
"amount": 123,
"amount_currency": "<string>",
"tag_ids": ["<string>"],
"add_tag_ids": ["<string>"],
"remove_tag_ids": ["<string>"],
"conversation_summary": {
"motivo": "<string>",
"estado": "<string>",
"proximos_pasos": "<string>",
"is_final": True
}
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.patch(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'PATCH',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
is_closed: true,
category: '<string>',
assigned_to: '<string>',
custom_fields: {},
amount: 123,
amount_currency: '<string>',
tag_ids: ['<string>'],
add_tag_ids: ['<string>'],
remove_tag_ids: ['<string>'],
conversation_summary: {
motivo: '<string>',
estado: '<string>',
proximos_pasos: '<string>',
is_final: true
}
})
};
fetch('https://api.keebai.com/v1/crm/tickets/{id}', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.keebai.com/v1/crm/tickets/{id}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "PATCH",
CURLOPT_POSTFIELDS => json_encode([
'is_closed' => true,
'category' => '<string>',
'assigned_to' => '<string>',
'custom_fields' => [
],
'amount' => 123,
'amount_currency' => '<string>',
'tag_ids' => [
'<string>'
],
'add_tag_ids' => [
'<string>'
],
'remove_tag_ids' => [
'<string>'
],
'conversation_summary' => [
'motivo' => '<string>',
'estado' => '<string>',
'proximos_pasos' => '<string>',
'is_final' => true
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.keebai.com/v1/crm/tickets/{id}"
payload := strings.NewReader("{\n \"is_closed\": true,\n \"category\": \"<string>\",\n \"assigned_to\": \"<string>\",\n \"custom_fields\": {},\n \"amount\": 123,\n \"amount_currency\": \"<string>\",\n \"tag_ids\": [\n \"<string>\"\n ],\n \"add_tag_ids\": [\n \"<string>\"\n ],\n \"remove_tag_ids\": [\n \"<string>\"\n ],\n \"conversation_summary\": {\n \"motivo\": \"<string>\",\n \"estado\": \"<string>\",\n \"proximos_pasos\": \"<string>\",\n \"is_final\": true\n }\n}")
req, _ := http.NewRequest("PATCH", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.patch("https://api.keebai.com/v1/crm/tickets/{id}")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"is_closed\": true,\n \"category\": \"<string>\",\n \"assigned_to\": \"<string>\",\n \"custom_fields\": {},\n \"amount\": 123,\n \"amount_currency\": \"<string>\",\n \"tag_ids\": [\n \"<string>\"\n ],\n \"add_tag_ids\": [\n \"<string>\"\n ],\n \"remove_tag_ids\": [\n \"<string>\"\n ],\n \"conversation_summary\": {\n \"motivo\": \"<string>\",\n \"estado\": \"<string>\",\n \"proximos_pasos\": \"<string>\",\n \"is_final\": true\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.keebai.com/v1/crm/tickets/{id}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Patch.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"is_closed\": true,\n \"category\": \"<string>\",\n \"assigned_to\": \"<string>\",\n \"custom_fields\": {},\n \"amount\": 123,\n \"amount_currency\": \"<string>\",\n \"tag_ids\": [\n \"<string>\"\n ],\n \"add_tag_ids\": [\n \"<string>\"\n ],\n \"remove_tag_ids\": [\n \"<string>\"\n ],\n \"conversation_summary\": {\n \"motivo\": \"<string>\",\n \"estado\": \"<string>\",\n \"proximos_pasos\": \"<string>\",\n \"is_final\": true\n }\n}"
response = http.request(request)
puts response.read_body{
"ticket": {
"_id": "<string>",
"chat_user": "<string>",
"is_closed": true,
"tags": [
{
"id": "<string>",
"name": "<string>",
"color": "<string>"
}
],
"custom_fields": {},
"created_at": "2023-11-07T05:31:56Z",
"updated_at": "2023-11-07T05:31:56Z",
"chat_user_name": "<string>",
"chat_user_avatar": "<string>",
"chat_user_channel": "<string>",
"chat_user_channel_id": "<string>",
"stage": {
"stage_id": "<string>",
"prioridad": "<string>",
"contexto": "<string>",
"problema": "<string>",
"recomendaciones": "<string>"
},
"category": "<string>",
"assigned_to": {
"id": "<string>",
"full_name": "<string>",
"email": "<string>"
},
"amount": 123,
"amount_currency": "<string>",
"conversation_summary": {
"motivo": "<string>",
"estado": "<string>",
"proximos_pasos": "<string>",
"is_final": true
},
"ai_feedback_status": "<string>",
"appointment_ids": [
"<string>"
],
"last_message": {
"id": "<string>",
"content": "<string>",
"role": "<string>",
"direction": "<string>",
"created_at": "2023-11-07T05:31:56Z"
},
"closed_at": "2023-11-07T05:31:56Z"
},
"just_closed": true,
"action": "<string>"
}Tickets
PATCH /v1/crm/tickets/:id
Partially update a ticket — close it, reassign it, retag it, or record its value.
PATCH
/
v1
/
crm
/
tickets
/
{id}
Actualizar un ticket
curl --request PATCH \
--url https://api.keebai.com/v1/crm/tickets/{id} \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"is_closed": true,
"category": "<string>",
"assigned_to": "<string>",
"custom_fields": {},
"amount": 123,
"amount_currency": "<string>",
"tag_ids": [
"<string>"
],
"add_tag_ids": [
"<string>"
],
"remove_tag_ids": [
"<string>"
],
"conversation_summary": {
"motivo": "<string>",
"estado": "<string>",
"proximos_pasos": "<string>",
"is_final": true
}
}
'import requests
url = "https://api.keebai.com/v1/crm/tickets/{id}"
payload = {
"is_closed": True,
"category": "<string>",
"assigned_to": "<string>",
"custom_fields": {},
"amount": 123,
"amount_currency": "<string>",
"tag_ids": ["<string>"],
"add_tag_ids": ["<string>"],
"remove_tag_ids": ["<string>"],
"conversation_summary": {
"motivo": "<string>",
"estado": "<string>",
"proximos_pasos": "<string>",
"is_final": True
}
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.patch(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'PATCH',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
is_closed: true,
category: '<string>',
assigned_to: '<string>',
custom_fields: {},
amount: 123,
amount_currency: '<string>',
tag_ids: ['<string>'],
add_tag_ids: ['<string>'],
remove_tag_ids: ['<string>'],
conversation_summary: {
motivo: '<string>',
estado: '<string>',
proximos_pasos: '<string>',
is_final: true
}
})
};
fetch('https://api.keebai.com/v1/crm/tickets/{id}', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.keebai.com/v1/crm/tickets/{id}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "PATCH",
CURLOPT_POSTFIELDS => json_encode([
'is_closed' => true,
'category' => '<string>',
'assigned_to' => '<string>',
'custom_fields' => [
],
'amount' => 123,
'amount_currency' => '<string>',
'tag_ids' => [
'<string>'
],
'add_tag_ids' => [
'<string>'
],
'remove_tag_ids' => [
'<string>'
],
'conversation_summary' => [
'motivo' => '<string>',
'estado' => '<string>',
'proximos_pasos' => '<string>',
'is_final' => true
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.keebai.com/v1/crm/tickets/{id}"
payload := strings.NewReader("{\n \"is_closed\": true,\n \"category\": \"<string>\",\n \"assigned_to\": \"<string>\",\n \"custom_fields\": {},\n \"amount\": 123,\n \"amount_currency\": \"<string>\",\n \"tag_ids\": [\n \"<string>\"\n ],\n \"add_tag_ids\": [\n \"<string>\"\n ],\n \"remove_tag_ids\": [\n \"<string>\"\n ],\n \"conversation_summary\": {\n \"motivo\": \"<string>\",\n \"estado\": \"<string>\",\n \"proximos_pasos\": \"<string>\",\n \"is_final\": true\n }\n}")
req, _ := http.NewRequest("PATCH", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.patch("https://api.keebai.com/v1/crm/tickets/{id}")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"is_closed\": true,\n \"category\": \"<string>\",\n \"assigned_to\": \"<string>\",\n \"custom_fields\": {},\n \"amount\": 123,\n \"amount_currency\": \"<string>\",\n \"tag_ids\": [\n \"<string>\"\n ],\n \"add_tag_ids\": [\n \"<string>\"\n ],\n \"remove_tag_ids\": [\n \"<string>\"\n ],\n \"conversation_summary\": {\n \"motivo\": \"<string>\",\n \"estado\": \"<string>\",\n \"proximos_pasos\": \"<string>\",\n \"is_final\": true\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.keebai.com/v1/crm/tickets/{id}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Patch.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"is_closed\": true,\n \"category\": \"<string>\",\n \"assigned_to\": \"<string>\",\n \"custom_fields\": {},\n \"amount\": 123,\n \"amount_currency\": \"<string>\",\n \"tag_ids\": [\n \"<string>\"\n ],\n \"add_tag_ids\": [\n \"<string>\"\n ],\n \"remove_tag_ids\": [\n \"<string>\"\n ],\n \"conversation_summary\": {\n \"motivo\": \"<string>\",\n \"estado\": \"<string>\",\n \"proximos_pasos\": \"<string>\",\n \"is_final\": true\n }\n}"
response = http.request(request)
puts response.read_body{
"ticket": {
"_id": "<string>",
"chat_user": "<string>",
"is_closed": true,
"tags": [
{
"id": "<string>",
"name": "<string>",
"color": "<string>"
}
],
"custom_fields": {},
"created_at": "2023-11-07T05:31:56Z",
"updated_at": "2023-11-07T05:31:56Z",
"chat_user_name": "<string>",
"chat_user_avatar": "<string>",
"chat_user_channel": "<string>",
"chat_user_channel_id": "<string>",
"stage": {
"stage_id": "<string>",
"prioridad": "<string>",
"contexto": "<string>",
"problema": "<string>",
"recomendaciones": "<string>"
},
"category": "<string>",
"assigned_to": {
"id": "<string>",
"full_name": "<string>",
"email": "<string>"
},
"amount": 123,
"amount_currency": "<string>",
"conversation_summary": {
"motivo": "<string>",
"estado": "<string>",
"proximos_pasos": "<string>",
"is_final": true
},
"ai_feedback_status": "<string>",
"appointment_ids": [
"<string>"
],
"last_message": {
"id": "<string>",
"content": "<string>",
"role": "<string>",
"direction": "<string>",
"created_at": "2023-11-07T05:31:56Z"
},
"closed_at": "2023-11-07T05:31:56Z"
},
"just_closed": true,
"action": "<string>"
}Updates a ticket in place. Only the fields you send are written. This is also how you close a ticket and record whether it was won or lost.
To move a ticket between pipeline stages, use
POST /v1/crm/tickets/:id/stage instead — stage transitions run pipeline rules that a plain field write does not.
Endpoint
PATCH https://api.keebai.com/v1/crm/tickets/{id}
Required scope
crm:tickets:write
Headers
| Header | Required | Value |
|---|---|---|
Authorization | Yes | Bearer kbai_pk_<token> |
Content-Type | Yes | application/json |
Path parameters
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | ObjectId of the ticket. |
Body
All fields optional. Send only what changes.| Field | Type | Description |
|---|---|---|
is_closed | boolean | Closes or reopens the ticket. |
resolution | string | won or lost. Meaningful when closing. |
priority | string | One of low, medium, high, urgent. |
category | string | Free-text category. |
assigned_to | string | ObjectId of the new owner. |
custom_fields | object | Replaces the whole custom fields object. |
amount | number | Monetary value of the opportunity. |
amount_currency | string | ISO 4217 currency. |
amount_status | string | estimated or confirmed. |
tag_ids | string[] | Replaces the whole tag list. |
add_tag_ids | string[] | Adds tags, leaving existing ones alone. |
remove_tag_ids | string[] | Removes tags, leaving the rest alone. |
conversation_summary | object | { motivo, estado, proximos_pasos, is_final }, all optional. |
Example request
curl -X PATCH https://api.keebai.com/v1/crm/tickets/65f4b5c6d7e8f9a0b1c2d3e4 \
-H "Authorization: Bearer kbai_pk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"is_closed": true,
"resolution": "won",
"amount": 180000,
"amount_status": "confirmed",
"add_tag_ids": ["69f1a2b3c4d5e6f7a8b9c0d1"]
}'
const response = await fetch(
`https://api.keebai.com/v1/crm/tickets/${ticketId}`,
{
method: "PATCH",
headers: {
Authorization: `Bearer ${process.env.KEEBAI_API_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
is_closed: true,
resolution: "won",
amount_status: "confirmed",
}),
},
);
const result = await response.json();
import os, requests
resp = requests.patch(
f"https://api.keebai.com/v1/crm/tickets/{ticket_id}",
headers={"Authorization": f"Bearer {os.environ['KEEBAI_API_TOKEN']}"},
json={
"is_closed": True,
"resolution": "won",
"amount_status": "confirmed",
},
timeout=10,
)
resp.raise_for_status()
result = resp.json()
Response
200 OK
{
"ticket": {
"_id": "65f4b5c6d7e8f9a0b1c2d3e4",
"is_closed": true,
"resolution": "won",
"amount": 180000,
"amount_currency": "CLP",
"amount_status": "confirmed",
"closed_at": "2026-05-02T16:10:22.114Z",
"updated_at": "2026-05-02T16:10:22.114Z"
},
"just_closed": true,
"action": "closed"
}
just_closed is true only on the request that flipped the ticket from open to closed, and action is closed on that same request and updated otherwise. Use just_closed to fire “deal won” side effects exactly once — a repeated PATCH with the same body returns false.
400 Bad Request
An enum value outside the allowed set, a malformedObjectId, or a property the endpoint does not accept.
401 Unauthorized
Missing, invalid, revoked, or expired token.403 Forbidden
The token does not have thecrm:tickets:write scope.
404 Not Found
No ticket with that id, or the ticket belongs to a different company.Operational notes
- Prefer
add_tag_ids/remove_tag_idsovertag_ids. The plural-only form replaces the list wholesale, so two systems tagging the same ticket will clobber each other. custom_fieldsreplaces too. Read the ticket first if you only mean to set one key.- Reopening a ticket is
{"is_closed": false}. The previousresolutionis not cleared automatically — send it explicitly if it no longer applies.
Authorizations
Personal Access Token con prefijo kbai_pk_. Generar desde el portal con permiso developer.manage_tokens.
Path Parameters
Body
application/json
Available options:
won, lost Cierra o reabre el ticket
Available options:
low, medium, high, urgent Available options:
estimated, confirmed Reemplaza la lista completa de tags
Agrega tags sin tocar las existentes
Quita tags sin tocar las demás
Show child attributes
Show child attributes
Was this page helpful?
⌘I