Crear una funcion de webhook
curl --request POST \
--url https://api.keebai.com/v1/tools \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"name": "consultar_stock",
"display_name": "Consultar stock",
"description": "Devuelve el stock disponible de un SKU en el ERP.",
"context": {
"url": "https://erp.example.com/stock",
"method": "POST",
"headers": {
"Authorization": "Bearer sk_live_xxx"
},
"params": {},
"body": {
"sku": "{{sku}}"
},
"timeout": 30,
"response_path": "<string>"
},
"tool_params": [
{
"name": "<string>",
"description": "<string>",
"type": "string",
"required": true
}
],
"tags": [
"<string>"
],
"is_active": true
}
'import requests
url = "https://api.keebai.com/v1/tools"
payload = {
"name": "consultar_stock",
"display_name": "Consultar stock",
"description": "Devuelve el stock disponible de un SKU en el ERP.",
"context": {
"url": "https://erp.example.com/stock",
"method": "POST",
"headers": { "Authorization": "Bearer sk_live_xxx" },
"params": {},
"body": { "sku": "{{sku}}" },
"timeout": 30,
"response_path": "<string>"
},
"tool_params": [
{
"name": "<string>",
"description": "<string>",
"type": "string",
"required": True
}
],
"tags": ["<string>"],
"is_active": True
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
name: 'consultar_stock',
display_name: 'Consultar stock',
description: 'Devuelve el stock disponible de un SKU en el ERP.',
context: {
url: 'https://erp.example.com/stock',
method: 'POST',
headers: {Authorization: 'Bearer sk_live_xxx'},
params: {},
body: JSON.stringify({sku: '{{sku}}'}),
timeout: 30,
response_path: '<string>'
},
tool_params: [{name: '<string>', description: '<string>', type: 'string', required: true}],
tags: ['<string>'],
is_active: true
})
};
fetch('https://api.keebai.com/v1/tools', 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/tools",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'name' => 'consultar_stock',
'display_name' => 'Consultar stock',
'description' => 'Devuelve el stock disponible de un SKU en el ERP.',
'context' => [
'url' => 'https://erp.example.com/stock',
'method' => 'POST',
'headers' => [
'Authorization' => 'Bearer sk_live_xxx'
],
'params' => [
],
'body' => [
'sku' => '{{sku}}'
],
'timeout' => 30,
'response_path' => '<string>'
],
'tool_params' => [
[
'name' => '<string>',
'description' => '<string>',
'type' => 'string',
'required' => true
]
],
'tags' => [
'<string>'
],
'is_active' => 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/tools"
payload := strings.NewReader("{\n \"name\": \"consultar_stock\",\n \"display_name\": \"Consultar stock\",\n \"description\": \"Devuelve el stock disponible de un SKU en el ERP.\",\n \"context\": {\n \"url\": \"https://erp.example.com/stock\",\n \"method\": \"POST\",\n \"headers\": {\n \"Authorization\": \"Bearer sk_live_xxx\"\n },\n \"params\": {},\n \"body\": {\n \"sku\": \"{{sku}}\"\n },\n \"timeout\": 30,\n \"response_path\": \"<string>\"\n },\n \"tool_params\": [\n {\n \"name\": \"<string>\",\n \"description\": \"<string>\",\n \"type\": \"string\",\n \"required\": true\n }\n ],\n \"tags\": [\n \"<string>\"\n ],\n \"is_active\": true\n}")
req, _ := http.NewRequest("POST", 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.post("https://api.keebai.com/v1/tools")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"name\": \"consultar_stock\",\n \"display_name\": \"Consultar stock\",\n \"description\": \"Devuelve el stock disponible de un SKU en el ERP.\",\n \"context\": {\n \"url\": \"https://erp.example.com/stock\",\n \"method\": \"POST\",\n \"headers\": {\n \"Authorization\": \"Bearer sk_live_xxx\"\n },\n \"params\": {},\n \"body\": {\n \"sku\": \"{{sku}}\"\n },\n \"timeout\": 30,\n \"response_path\": \"<string>\"\n },\n \"tool_params\": [\n {\n \"name\": \"<string>\",\n \"description\": \"<string>\",\n \"type\": \"string\",\n \"required\": true\n }\n ],\n \"tags\": [\n \"<string>\"\n ],\n \"is_active\": true\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.keebai.com/v1/tools")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"name\": \"consultar_stock\",\n \"display_name\": \"Consultar stock\",\n \"description\": \"Devuelve el stock disponible de un SKU en el ERP.\",\n \"context\": {\n \"url\": \"https://erp.example.com/stock\",\n \"method\": \"POST\",\n \"headers\": {\n \"Authorization\": \"Bearer sk_live_xxx\"\n },\n \"params\": {},\n \"body\": {\n \"sku\": \"{{sku}}\"\n },\n \"timeout\": 30,\n \"response_path\": \"<string>\"\n },\n \"tool_params\": [\n {\n \"name\": \"<string>\",\n \"description\": \"<string>\",\n \"type\": \"string\",\n \"required\": true\n }\n ],\n \"tags\": [\n \"<string>\"\n ],\n \"is_active\": true\n}"
response = http.request(request)
puts response.read_body{
"tool": {
"_id": "<string>",
"name": "<string>",
"display_name": "<string>",
"description": "<string>",
"type": "webhook",
"tool_params": [
{
"name": "<string>",
"type": "string",
"required": true,
"description": "<string>"
}
],
"context": {
"url": "<string>",
"method": "GET",
"headers": {
"Authorization": "***"
},
"params": {},
"timeout": 123,
"body": {},
"response_path": "<string>"
},
"tags": [
"<string>"
],
"is_active": true,
"created_at": "<string>",
"updated_at": "<string>"
}
}POST /v1/tools
Create a webhook tool an agent can call, with its URL, headers and parameters.
POST
/
v1
/
tools
Crear una funcion de webhook
curl --request POST \
--url https://api.keebai.com/v1/tools \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"name": "consultar_stock",
"display_name": "Consultar stock",
"description": "Devuelve el stock disponible de un SKU en el ERP.",
"context": {
"url": "https://erp.example.com/stock",
"method": "POST",
"headers": {
"Authorization": "Bearer sk_live_xxx"
},
"params": {},
"body": {
"sku": "{{sku}}"
},
"timeout": 30,
"response_path": "<string>"
},
"tool_params": [
{
"name": "<string>",
"description": "<string>",
"type": "string",
"required": true
}
],
"tags": [
"<string>"
],
"is_active": true
}
'import requests
url = "https://api.keebai.com/v1/tools"
payload = {
"name": "consultar_stock",
"display_name": "Consultar stock",
"description": "Devuelve el stock disponible de un SKU en el ERP.",
"context": {
"url": "https://erp.example.com/stock",
"method": "POST",
"headers": { "Authorization": "Bearer sk_live_xxx" },
"params": {},
"body": { "sku": "{{sku}}" },
"timeout": 30,
"response_path": "<string>"
},
"tool_params": [
{
"name": "<string>",
"description": "<string>",
"type": "string",
"required": True
}
],
"tags": ["<string>"],
"is_active": True
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
name: 'consultar_stock',
display_name: 'Consultar stock',
description: 'Devuelve el stock disponible de un SKU en el ERP.',
context: {
url: 'https://erp.example.com/stock',
method: 'POST',
headers: {Authorization: 'Bearer sk_live_xxx'},
params: {},
body: JSON.stringify({sku: '{{sku}}'}),
timeout: 30,
response_path: '<string>'
},
tool_params: [{name: '<string>', description: '<string>', type: 'string', required: true}],
tags: ['<string>'],
is_active: true
})
};
fetch('https://api.keebai.com/v1/tools', 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/tools",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'name' => 'consultar_stock',
'display_name' => 'Consultar stock',
'description' => 'Devuelve el stock disponible de un SKU en el ERP.',
'context' => [
'url' => 'https://erp.example.com/stock',
'method' => 'POST',
'headers' => [
'Authorization' => 'Bearer sk_live_xxx'
],
'params' => [
],
'body' => [
'sku' => '{{sku}}'
],
'timeout' => 30,
'response_path' => '<string>'
],
'tool_params' => [
[
'name' => '<string>',
'description' => '<string>',
'type' => 'string',
'required' => true
]
],
'tags' => [
'<string>'
],
'is_active' => 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/tools"
payload := strings.NewReader("{\n \"name\": \"consultar_stock\",\n \"display_name\": \"Consultar stock\",\n \"description\": \"Devuelve el stock disponible de un SKU en el ERP.\",\n \"context\": {\n \"url\": \"https://erp.example.com/stock\",\n \"method\": \"POST\",\n \"headers\": {\n \"Authorization\": \"Bearer sk_live_xxx\"\n },\n \"params\": {},\n \"body\": {\n \"sku\": \"{{sku}}\"\n },\n \"timeout\": 30,\n \"response_path\": \"<string>\"\n },\n \"tool_params\": [\n {\n \"name\": \"<string>\",\n \"description\": \"<string>\",\n \"type\": \"string\",\n \"required\": true\n }\n ],\n \"tags\": [\n \"<string>\"\n ],\n \"is_active\": true\n}")
req, _ := http.NewRequest("POST", 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.post("https://api.keebai.com/v1/tools")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"name\": \"consultar_stock\",\n \"display_name\": \"Consultar stock\",\n \"description\": \"Devuelve el stock disponible de un SKU en el ERP.\",\n \"context\": {\n \"url\": \"https://erp.example.com/stock\",\n \"method\": \"POST\",\n \"headers\": {\n \"Authorization\": \"Bearer sk_live_xxx\"\n },\n \"params\": {},\n \"body\": {\n \"sku\": \"{{sku}}\"\n },\n \"timeout\": 30,\n \"response_path\": \"<string>\"\n },\n \"tool_params\": [\n {\n \"name\": \"<string>\",\n \"description\": \"<string>\",\n \"type\": \"string\",\n \"required\": true\n }\n ],\n \"tags\": [\n \"<string>\"\n ],\n \"is_active\": true\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.keebai.com/v1/tools")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"name\": \"consultar_stock\",\n \"display_name\": \"Consultar stock\",\n \"description\": \"Devuelve el stock disponible de un SKU en el ERP.\",\n \"context\": {\n \"url\": \"https://erp.example.com/stock\",\n \"method\": \"POST\",\n \"headers\": {\n \"Authorization\": \"Bearer sk_live_xxx\"\n },\n \"params\": {},\n \"body\": {\n \"sku\": \"{{sku}}\"\n },\n \"timeout\": 30,\n \"response_path\": \"<string>\"\n },\n \"tool_params\": [\n {\n \"name\": \"<string>\",\n \"description\": \"<string>\",\n \"type\": \"string\",\n \"required\": true\n }\n ],\n \"tags\": [\n \"<string>\"\n ],\n \"is_active\": true\n}"
response = http.request(request)
puts response.read_body{
"tool": {
"_id": "<string>",
"name": "<string>",
"display_name": "<string>",
"description": "<string>",
"type": "webhook",
"tool_params": [
{
"name": "<string>",
"type": "string",
"required": true,
"description": "<string>"
}
],
"context": {
"url": "<string>",
"method": "GET",
"headers": {
"Authorization": "***"
},
"params": {},
"timeout": 123,
"body": {},
"response_path": "<string>"
},
"tags": [
"<string>"
],
"is_active": true,
"created_at": "<string>",
"updated_at": "<string>"
}
}Creates a webhook tool. You describe what it does and which arguments it takes; the model decides when to call it and with what.
Placeholders written
{{name}} in the path, the query string or the body are filled at call time from the arguments the model chose, so https://erp.example.com/stock/{{sku}} with a sku parameter is the normal shape. Not in the host — read why.
Endpoint
POST https://api.keebai.com/v1/tools
Required scope
agents:write
Headers
| Header | Required | Value |
|---|---|---|
Authorization | Yes | Bearer kbai_pk_<token> |
Content-Type | Yes | application/json |
Body
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Machine name, unique per company. Up to 100 characters. The model calls it by this. |
display_name | string | Yes | Up to 200 characters. Shown in the portal. |
description | string | Yes | Up to 1000 characters. The model reads this to decide when to call the tool — write it for the model, not for a human. |
context | object | Yes | The HTTP request to make. |
context.url | string | Yes | Public https URL. Validated — see the rules. |
context.method | string | No | GET, POST, PUT, PATCH or DELETE. Defaults to POST. |
context.headers | object | No | String values. Write-only: reads return ***. |
context.params | object | No | Query string. Write-only, same as headers. |
context.body | object | No | Request body template. Returned unmasked. |
context.timeout | integer | No | 1–30 seconds. Defaults to 30. |
context.response_path | string | No | Dotted path into the JSON response to extract, e.g. data.items. Without it the whole response is handed to the model. |
tool_params | object[] | No | Up to 30 arguments the model can supply. |
tool_params[].name | string | Yes | Argument name, matching the {{placeholder}}. |
tool_params[].description | string | No | What it means. The model reads this too. |
tool_params[].type | string | No | string, number, boolean, array or object. Defaults to string. |
tool_params[].required | boolean | No | Defaults to true. |
tags | string[] | No | Up to 20, for grouping in the portal. |
is_active | boolean | No | Defaults to true. |
Example request
curl -X POST https://api.keebai.com/v1/tools \
-H "Authorization: Bearer kbai_pk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"name": "consultar_stock",
"display_name": "Consultar stock",
"description": "Devuelve el stock disponible de un SKU en el ERP. Usar antes de prometer disponibilidad.",
"context": {
"url": "https://erp.example.com/stock/{{sku}}",
"method": "GET",
"headers": { "Authorization": "Bearer erp_live_xxx" },
"timeout": 10,
"response_path": "data.available"
},
"tool_params": [
{ "name": "sku", "description": "Código del producto", "required": true }
],
"tags": ["erp"]
}'
const resp = await fetch("https://api.keebai.com/v1/tools", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.KEEBAI_API_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "consultar_stock",
display_name: "Consultar stock",
description: "Devuelve el stock disponible de un SKU en el ERP.",
context: {
url: "https://erp.example.com/stock/{{sku}}",
method: "GET",
headers: { Authorization: `Bearer ${process.env.ERP_TOKEN}` },
timeout: 10,
},
tool_params: [{ name: "sku", description: "Código del producto" }],
}),
});
if (resp.status === 400) {
const { error } = await resp.json();
if (error.code === "FUNCTION_URL_NOT_ALLOWED") {
console.error(error.details.reason);
}
}
import os, requests
resp = requests.post(
"https://api.keebai.com/v1/tools",
headers={"Authorization": f"Bearer {os.environ['KEEBAI_API_TOKEN']}"},
json={
"name": "consultar_stock",
"display_name": "Consultar stock",
"description": "Devuelve el stock disponible de un SKU en el ERP.",
"context": {
"url": "https://erp.example.com/stock/{{sku}}",
"method": "GET",
"headers": {"Authorization": f"Bearer {os.environ['ERP_TOKEN']}"},
"timeout": 10,
},
"tool_params": [{"name": "sku", "description": "Código del producto"}],
},
timeout=10,
)
resp.raise_for_status()
tool = resp.json()["tool"]
Response
201 Created
The tool, same shape asGET /v1/tools/{id}, with the header values already masked.
400 Bad Request
A missing required field, a field over its limit, or an unknown property. Two specific codes:FUNCTION_URL_NOT_ALLOWED— the URL failed validation.details.reasonnames the rule; see the table.FUNCTION_TIMEOUT_NOT_ALLOWED—timeoutoutside 1–30 seconds.
401 Unauthorized
Missing, invalid, revoked, or expired token.403 Forbidden
The token does not have theagents:write scope.
409 Conflict
A tool with thatname already exists in the company.
502 Bad Gateway
The agent service is unreachable.Operational notes
descriptionis a prompt, not documentation. It is the only thing the model reads when deciding whether to call the tool, so say when to use it and when not to. “Devuelve el stock. Usar antes de prometer disponibilidad” beats “Endpoint de stock”.- A
tool_paramsentry with no matching placeholder is still passed to the model, which will supply it and see it go nowhere. Keep the two in sync. nameis unique per company, not per project. A409can come from a tool created under a different project.- Creating a tool does not attach it to anything. An agent can only call the tools whose ids it holds; add it with
PATCH /v1/agents/{id}. typeis forced towebhook. Sending anything else is rejected: the calendar and ecommerce tools are generated by Keebai and cannot be created through this API.
Authorizations
Personal Access Token con prefijo kbai_pk_. Generar desde el portal con permiso developer.manage_tokens.
Body
application/json
Nombre tecnico, unico por company. Es el nombre con el que el modelo invoca la herramienta.
Maximum string length:
100Example:
"consultar_stock"
Maximum string length:
200Example:
"Consultar stock"
Para que sirve la herramienta. Es lo que lee el modelo para decidir cuando llamarla.
Maximum string length:
1000Example:
"Devuelve el stock disponible de un SKU en el ERP."
Show child attributes
Show child attributes
Maximum array length:
30Show child attributes
Show child attributes
Maximum array length:
20Response
201 - application/json
Show child attributes
Show child attributes
Was this page helpful?
⌘I