> ## 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/functions

> Create a webhook function an assistant can call, with its URL, headers and parameters.

Creates a webhook function. 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](/dev/assistants/webhook-security#placeholders-in-the-host-are-the-interesting-case).

## Endpoint

```
POST https://api.keebai.com/v1/functions
```

## Required scope

`assistants: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 function** — 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](/dev/assistants/webhook-security#which-urls-are-rejected).                     |
| `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

<CodeGroup>
  ```bash curl theme={null}
  curl -X POST https://api.keebai.com/v1/functions \
    -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"]
    }'
  ```

  ```js JavaScript theme={null}
  const resp = await fetch("https://api.keebai.com/v1/functions", {
    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);
    }
  }
  ```

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

  resp = requests.post(
      "https://api.keebai.com/v1/functions",
      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()
  fn = resp.json()["function"]
  ```
</CodeGroup>

## Response

### 201 Created

The function, same shape as [`GET /v1/functions/{id}`](/dev/endpoints/functions-get), 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.reason` names the rule; see [the table](/dev/assistants/webhook-security#which-urls-are-rejected).
* `FUNCTION_TIMEOUT_NOT_ALLOWED` — `timeout` outside 1–30 seconds.

### 401 Unauthorized

Missing, invalid, revoked, or expired token.

### 403 Forbidden

The token does not have the `assistants:write` scope.

### 409 Conflict

A function with that `name` already exists in the company.

### 502 Bad Gateway

The assistant service is unreachable.

## Operational notes

* **`description` is a prompt, not documentation.** It is the only thing the model reads when deciding whether to call the function, so say when to use it and when not to. "Devuelve el stock. Usar antes de prometer disponibilidad" beats "Endpoint de stock".
* **A `tool_params` entry with no matching placeholder is still passed to the model**, which will supply it and see it go nowhere. Keep the two in sync.
* **`name` is unique per company, not per project.** A `409` can come from a function created under a different project.
* **Creating a function does not attach it to anything.** The model can only call functions bound to a block in the assistant's prompt; that binding is done in the portal.
* **`type` is forced to `webhook`.** Sending anything else is rejected, and there is no way to create a calendar, ecommerce or block-managed function through this API.


## OpenAPI

````yaml POST /v1/functions
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/functions:
    post:
      tags:
        - functions
      summary: Crear una funcion de webhook
      description: >-
        Crea una funcion que el asistente puede llamar. La URL tiene que ser
        https publica: se rechazan loopback, rangos privados, hosts internos del
        cluster y placeholders en el host.
      operationId: PublicFunctionsController_create
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PublicFunctionCreateDto'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicFunctionEnvelopeDto'
      security:
        - PAT: []
components:
  schemas:
    PublicFunctionCreateDto:
      type: object
      properties:
        name:
          type: string
          maxLength: 100
          description: >-
            Nombre tecnico, unico por company. Es el nombre con el que el modelo
            invoca la funcion.
          example: consultar_stock
        display_name:
          type: string
          maxLength: 200
          example: Consultar stock
        description:
          type: string
          maxLength: 1000
          description: >-
            Para que sirve la funcion. Es lo que lee el modelo para decidir
            cuando llamarla.
          example: Devuelve el stock disponible de un SKU en el ERP.
        context:
          $ref: '#/components/schemas/PublicWebhookContextDto'
        tool_params:
          maxItems: 30
          type: array
          items:
            $ref: '#/components/schemas/PublicFunctionParamDto'
        tags:
          maxItems: 20
          type: array
          items:
            type: string
        is_active:
          type: boolean
          default: true
      required:
        - name
        - display_name
        - description
        - context
    PublicFunctionEnvelopeDto:
      type: object
      properties:
        function:
          $ref: '#/components/schemas/PublicFunctionDto'
      required:
        - function
    PublicWebhookContextDto:
      type: object
      properties:
        url:
          type: string
          description: >-
            URL https publica. Se rechazan loopback, rangos privados, hosts
            internos del cluster y placeholders en el host.
          example: https://erp.example.com/stock
        method:
          type: string
          enum:
            - GET
            - POST
            - PUT
            - PATCH
            - DELETE
          default: POST
        headers:
          type: object
          description: >-
            Headers de la request. Los valores son de solo escritura: en lectura
            vuelven enmascarados. Omitir el campo en un update preserva los
            guardados; mandarlo los reemplaza completos.
          example:
            Authorization: Bearer sk_live_xxx
        params:
          type: object
          description: >-
            Query string de la request. Mismo trato que headers: enmascarado en
            lectura.
        body:
          type: object
          description: >-
            Body de la request. Acepta placeholders '{{arg}}' que se reemplazan
            con los argumentos del modelo.
          example:
            sku: '{{sku}}'
        timeout:
          type: number
          default: 30
          minimum: 1
          maximum: 30
          description: Segundos de espera antes de abortar la llamada.
        response_path:
          type: string
          maxLength: 200
          description: >-
            Ruta dentro del JSON de respuesta a extraer, por ejemplo
            'data.items'. Sin esto se entrega la respuesta completa.
      required:
        - url
    PublicFunctionParamDto:
      type: object
      properties:
        name:
          type: string
          maxLength: 100
          description: Nombre del argumento tal como lo va a pasar el modelo.
        description:
          type: string
          maxLength: 500
          description: >-
            Que significa el argumento. El modelo lee esto para decidir que
            mandar.
        type:
          type: string
          enum:
            - string
            - number
            - boolean
            - array
            - object
          default: string
        required:
          type: boolean
          default: true
      required:
        - name
    PublicFunctionDto:
      type: object
      properties:
        _id:
          type: string
        name:
          type: string
        display_name:
          type: string
        description:
          type: string
        type:
          type: string
          example: webhook
        tool_params:
          type: array
          items:
            $ref: '#/components/schemas/PublicFunctionParamResponseDto'
        context:
          $ref: '#/components/schemas/PublicWebhookContextResponseDto'
        tags:
          type: array
          items:
            type: string
        is_active:
          type: boolean
        used_by_blocks:
          description: >-
            Bloques que referencian esta funcion. Mientras la lista no este
            vacia, borrarla devuelve 409.
          type: array
          items:
            $ref: '#/components/schemas/PublicFunctionUsageDto'
        created_at:
          type: string
        updated_at:
          type: string
      required:
        - _id
        - name
        - display_name
        - description
        - type
        - tool_params
        - context
        - tags
        - is_active
        - used_by_blocks
        - created_at
        - updated_at
    PublicFunctionParamResponseDto:
      type: object
      properties:
        name:
          type: string
        description:
          type: string
        type:
          type: string
          enum:
            - string
            - number
            - boolean
            - array
            - object
        required:
          type: boolean
      required:
        - name
        - type
        - required
    PublicWebhookContextResponseDto:
      type: object
      properties:
        url:
          type: string
        method:
          type: string
          enum:
            - GET
            - POST
            - PUT
            - PATCH
            - DELETE
        headers:
          type: object
          additionalProperties:
            type: string
          description: >-
            Headers configurados. Las claves se muestran y los valores vuelven
            siempre como '***'.
          example:
            Authorization: '***'
        params:
          type: object
          additionalProperties:
            type: string
          description: >-
            Query string configurada, con los valores enmascarados igual que
            headers.
        body:
          type: object
          additionalProperties: true
          description: >-
            Body de la request. No se enmascara: es la plantilla que rellena el
            modelo.
        timeout:
          type: number
        response_path:
          type: string
      required:
        - url
        - method
        - headers
        - params
        - timeout
    PublicFunctionUsageDto:
      type: object
      properties:
        block_id:
          type: string
        name:
          type: string
        type:
          type: string
          enum:
            - personification
            - objective
            - response_format
            - steps
            - cases_possible
            - do_not
            - product_info
            - store_info
            - reservation_info
            - tables_info
            - tags
            - response_policy
      required:
        - block_id
        - name
        - type
  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`.

````