# Kadu — Documentação > Documentação do Kadu: Agent Console, Kadu Playbook, API v1 e servidor MCP. ## en Kobana's AI agent that runs your organization's recurring tasks. - [Kadu](https://docs.kadu.ia.br/en/index.md): Kobana's AI agent that runs your organization's recurring tasks. ### search - [Search the documentation](https://docs.kadu.ia.br/en/search.md) ### api - [API reference](https://docs.kadu.ia.br/en/api.md) #### overview - [Authentication](https://docs.kadu.ia.br/en/api/overview/authentication.md): The Kadu API uses a Kobana Account access token to authenticate every - [Conventions](https://docs.kadu.ia.br/en/api/overview/conventions.md): Format - [Data Retention](https://docs.kadu.ia.br/en/api/overview/data-retention.md): This page describes how long Kadu keeps each type of data. - [Endpoints](https://docs.kadu.ia.br/en/api/overview/endpoints.md): Platform (Agent Console) - [Errors](https://docs.kadu.ia.br/en/api/overview/errors.md): Every v1 API error comes in the same envelope: - [Idempotency](https://docs.kadu.ia.br/en/api/overview/idempotency.md): Every write (POST, PUT, PATCH) accepts the X-Idempotency-Key header. - [Introduction](https://docs.kadu.ia.br/en/api/overview/introduction.md): The Kadu v1 API gives another system everything the Agent Console does: - [OpenAPI](https://docs.kadu.ia.br/en/api/overview/openapi.md): API v1 is described by an OpenAPI 3.1 specification, generated from Kadu's - [Pagination, sorting and filters](https://docs.kadu.ia.br/en/api/overview/pagination.md): Cursor - [Permissions](https://docs.kadu.ia.br/en/api/overview/permissions.md): An authenticated request goes through three checks, in this order. Any one - [Rate limits](https://docs.kadu.ia.br/en/api/overview/rate-limit.md): | Where | Limit | Per | - [Run endpoint](https://docs.kadu.ia.br/en/api/overview/run-task-endpoint.md): The run endpoint lets another system run one specific task with an HTTP - [User-Agent](https://docs.kadu.ia.br/en/api/overview/user-agent.md): You can include the User-Agent header with your application's name and a valid - [Webhooks](https://docs.kadu.ia.br/en/api/overview/webhooks.md): A webhook delivers a signed JSON POST to the address you registered when an ### cli #### overview - [Authentication](https://docs.kadu.ia.br/en/cli/overview/authentication.md): KOBANA_TOKEN holds the credential of one product at a time. A Kadu Agent token does not authenticate on any other Kobana product, and vice versa. - [Environment variables](https://docs.kadu.ia.br/en/cli/overview/environment-variables.md): Every variable can also be set in a .env file (loaded via dotenvy). - [Exit codes](https://docs.kadu.ia.br/en/cli/overview/exit-codes.md): Every kobana run ends with one of these codes, so a script or agent can decide what to do without parsing the message. - [Installation](https://docs.kadu.ia.br/en/cli/overview/installation.md): Script (macOS and Linux) - [Introduction](https://docs.kadu.ia.br/en/cli/overview/introduction.md): kobana is a single command-line tool for the APIs of every Kobana product — the Kadu Agent included. It is built and maintained in its own repository (kobana-cli), not by this product, and has no hand-written endpoint at all: on startup it reads the OpenAPI 3.1 specs embedded in the binary and builds the command tree from them. Updating the API surface is a matter of updating the embedded spec and publishing a new build — no code per endpoint. #### resources - [Activities](https://docs.kadu.ia.br/en/cli/resources/activities.md): Groupings of tasks within a workspace. - [Environments](https://docs.kadu.ia.br/en/cli/resources/environments.md): Execution environments (Production, Sandbox…). Each secret and schedule belongs to one. - [Executions](https://docs.kadu.ia.br/en/cli/resources/executions.md): Each run of a task, with logs, events, pending items and text search. - [Files](https://docs.kadu.ia.br/en/cli/resources/files.md): Input files (attached to the run) and output files (published by the execution). - [Identity](https://docs.kadu.ia.br/en/cli/resources/me.md): Who is behind the token. - [Memberships](https://docs.kadu.ia.br/en/cli/resources/memberships.md): Links between people and the organization. - [Memories](https://docs.kadu.ia.br/en/cli/resources/memories.md): Organization memory: records per entity (entitytype + entityvalue), scoped to the organization, a workspace or an activity. - [Approvals](https://docs.kadu.ia.br/en/cli/resources/operation-approvals.md): Operations an assistant requested through MCP that wait for the person's confirmation by link (read-only). - [Organizations](https://docs.kadu.ia.br/en/cli/resources/organizations.md): The token's organization: name, time zone, monthly cost cap and custom_metadata. - [Resources](https://docs.kadu.ia.br/en/cli/resources/overview.md): Every kadu resource below is generated from the same OpenAPI spec documented in the API Reference — the CLI calls exactly the same endpoints, so filters, validation, scopes and deletion behavior are identical. Use schema introspection whenever the exact parameters of a command are not obvious from the examples on these pages. - [Repository](https://docs.kadu.ia.br/en/cli/resources/repository.md): The organization's Kadu Playbook and its sync. - [Roles](https://docs.kadu.ia.br/en/cli/resources/roles.md): Roles and the agent.* permissions they grant. - [Schedules](https://docs.kadu.ia.br/en/cli/resources/schedules.md): Recurring executions of a task in an environment, defined by a cron expression. - [Secrets](https://docs.kadu.ia.br/en/cli/resources/secrets.md): Credentials delivered to executions. The value is stored encrypted and is never returned. - [System events](https://docs.kadu.ia.br/en/cli/resources/system-events.md): Organization events (the same ones that feed webhooks). - [Tasks](https://docs.kadu.ia.br/en/cli/resources/tasks.md): The organization's executable skills: registration, run, pre-check and the HTTP run endpoint. - [Pending items](https://docs.kadu.ia.br/en/cli/resources/todos.md): Pending items created by executions. - [Usage](https://docs.kadu.ia.br/en/cli/resources/usage.md): Tokens, cost and price of executions. - [Users](https://docs.kadu.ia.br/en/cli/resources/users.md): Organization members. - [Webhook deliveries](https://docs.kadu.ia.br/en/cli/resources/webhook-deliveries.md): History and resending of webhook deliveries. - [Webhooks](https://docs.kadu.ia.br/en/cli/resources/webhooks.md): Signed HTTP notifications sent when events occur. - [Workspaces](https://docs.kadu.ia.br/en/cli/resources/workspaces.md): Business domains of the organization. Each workspace is a plugin of the Kadu Playbook. #### usage - [Global flags](https://docs.kadu.ia.br/en/cli/usage/global-flags.md): | Flag | Description | - [Helpers](https://docs.kadu.ia.br/en/cli/usage/helpers.md): Helpers are +-prefixed commands that deliver multi-step flows the dynamic commands alone do not: simplified interfaces, batch operations or calls composed of several endpoints. They do not wrap a single API call already available through the dynamic commands. - [Usage](https://docs.kadu.ia.br/en/cli/usage/overview.md): Syntax - [Schema introspection](https://docs.kadu.ia.br/en/cli/usage/schema.md): Returns parameters, required fields, types and responses — all derived from the embedded OpenAPI spec, so it is always in sync with what the command actually accepts. The path follows the same shape as the command: ... ### conceitos #### ambientes-e-secrets Where tasks run and how credentials reach them. - [Environments and secrets](https://docs.kadu.ia.br/en/conceitos/ambientes-e-secrets.md): Where tasks run and how credentials reach them. #### estrutura How Kadu organizes an organization's tasks. - [Organization, workspaces and activities](https://docs.kadu.ia.br/en/conceitos/estrutura.md): How Kadu organizes an organization's tasks. #### execucoes The life cycle of an execution, the per-task queue and what each execution records. - [Executions](https://docs.kadu.ia.br/en/conceitos/execucoes.md): The life cycle of an execution, the per-task queue and what each execution records. #### plano-e-custos Contracted plan, credits, task tiers, the cost and price of executions and conversations and the monthly cap. - [Plan and costs](https://docs.kadu.ia.br/en/conceitos/plano-e-custos.md): Contracted plan, credits, task tiers, the cost and price of executions and conversations and the monthly cap. #### tarefas-e-skills What a skill is, when it becomes a task and what the task declares. - [Tasks and skills](https://docs.kadu.ia.br/en/conceitos/tarefas-e-skills.md): What a skill is, when it becomes a task and what the task declares. ### configuracoes #### agendamentos Run a task on its own, by cron expression, in an environment. - [Schedules](https://docs.kadu.ia.br/en/configuracoes/agendamentos.md): Run a task on its own, by cron expression, in an environment. #### ambientes Create, edit and delete execution environments. - [Environments](https://docs.kadu.ia.br/en/configuracoes/ambientes.md): Create, edit and delete execution environments. #### conteudo Workspaces, activities, tasks and the Playbook's skills. - [Content](https://docs.kadu.ia.br/en/configuracoes/conteudo.md): Workspaces, activities, tasks and the Playbook's skills. #### deployment The organization's Playbook, the sync and the latest commits. - [Deployment](https://docs.kadu.ia.br/en/configuracoes/deployment.md): The organization's Playbook, the sync and the latest commits. #### endpoints Tasks that another system triggers through an HTTP call. - [Endpoints](https://docs.kadu.ia.br/en/configuracoes/endpoints.md): Tasks that another system triggers through an HTTP call. #### secrets Register the credentials that tasks use. - [Secrets](https://docs.kadu.ia.br/en/configuracoes/secrets.md): Register the credentials that tasks use. #### slack Talk to Kadu in Slack, run Tasks and follow the execution in the thread. - [Slack](https://docs.kadu.ia.br/en/configuracoes/slack.md): Talk to Kadu in Slack, run Tasks and follow the execution in the thread. #### webhooks Receive execution and organization events in your system. - [Webhooks](https://docs.kadu.ia.br/en/configuracoes/webhooks.md): Receive execution and organization events in your system. ### console #### acompanhar-execucao The tabs of an execution, live logs, replay and cancellation. - [Follow an execution](https://docs.kadu.ia.br/en/console/acompanhar-execucao.md): The tabs of an execution, live logs, replay and cancellation. #### analytics Token usage and cost of executions and Slack conversations, and the list of all executions. - [Analytics](https://docs.kadu.ia.br/en/console/analytics.md): Token usage and cost of executions and Slack conversations, and the list of all executions. #### arquivos Input and output files of executions. - [Files](https://docs.kadu.ia.br/en/console/arquivos.md): Input and output files of executions. #### executar-tarefa The Run now modal, parameters, input files and the queue. - [Run a task](https://docs.kadu.ia.br/en/console/executar-tarefa.md): The Run now modal, parameters, input files and the queue. #### navegacao Top bar, task selection, execution list and search. - [Navigating the Agent Console](https://docs.kadu.ia.br/en/console/navegacao.md): Top bar, task selection, execution list and search. #### pendencias What a pending item is, how to resolve it and the Resolve pending mode. - [Pending items](https://docs.kadu.ia.br/en/console/pendencias.md): What a pending item is, how to resolve it and the Resolve pending mode. ### docs-mcp This documentation has its own MCP server. It lets an AI assistant (Claude, - [Docs MCP](https://docs.kadu.ia.br/en/docs-mcp.md): This documentation has its own MCP server. It lets an AI assistant (Claude, ### mcp #### overview - [Authentication](https://docs.kadu.ia.br/en/mcp/overview/authentication.md): The MCP server accepts a Bearer token in the Authorization header, and - [Clients](https://docs.kadu.ia.br/en/mcp/overview/clients.md): Any client that speaks Streamable HTTP MCP and can run the MCP OAuth - [Endpoints](https://docs.kadu.ia.br/en/mcp/overview/endpoints.md): MCP server - [Errors](https://docs.kadu.ia.br/en/mcp/overview/errors.md): Failures arrive through two different channels, and the distinction - [Idempotency](https://docs.kadu.ia.br/en/mcp/overview/idempotency.md): The tools that change data — all of them except the read and delete tools - [Introduction](https://docs.kadu.ia.br/en/mcp/overview/introduction.md): The Kadu MCP server is a remote Model Context Protocol - [Listing, Sorting and Filters](https://docs.kadu.ia.br/en/mcp/overview/pagination.md): The _list tools use the same pagination as the v1 API: by cursor* (see - [Rate Limiting](https://docs.kadu.ia.br/en/mcp/overview/rate-limit.md): The MCP server accepts 600 requests per minute per person and - [Resources and prompts](https://docs.kadu.ia.br/en/mcp/overview/resources-and-prompts.md): Besides tools, the server offers three things the client uses on its own: - [Transport](https://docs.kadu.ia.br/en/mcp/overview/transport.md): Streamable HTTP, stateless. One JSON-RPC 2.0 message per POST, one JSON #### tools - [Approvals and events](https://docs.kadu.ia.br/en/mcp/tools/approvals.md): {/ Page generated by scripts/generate-mcp-user-docs.ts from the tool registry. Do not edit by handgenerate. /} - [Configuration and files](https://docs.kadu.ia.br/en/mcp/tools/configuration.md): {/ Page generated by scripts/generate-mcp-user-docs.ts from the tool registry. Do not edit by handgenerate. /} - [Executions](https://docs.kadu.ia.br/en/mcp/tools/executions.md): {/ Page generated by scripts/generate-mcp-user-docs.ts from the tool registry. Do not edit by handgenerate. /} - [Organization and team](https://docs.kadu.ia.br/en/mcp/tools/organization.md): {/ Page generated by scripts/generate-mcp-user-docs.ts from the tool registry. Do not edit by handgenerate. /} - [Tools](https://docs.kadu.ia.br/en/mcp/tools/overview.md): {/ Page generated by scripts/generate-mcp-user-docs.ts from the tool registry. Do not edit by handgenerate. /} - [Playbook and usage](https://docs.kadu.ia.br/en/mcp/tools/repository.md): {/ Page generated by scripts/generate-mcp-user-docs.ts from the tool registry. Do not edit by handgenerate. /} - [Structure and tasks](https://docs.kadu.ia.br/en/mcp/tools/tasks.md): {/ Page generated by scripts/generate-mcp-user-docs.ts from the tool registry. Do not edit by handgenerate. /} - [Webhooks](https://docs.kadu.ia.br/en/mcp/tools/webhooks.md): {/ Page generated by scripts/generate-mcp-user-docs.ts from the tool registry. Do not edit by handgenerate. /} ### playbook The Kadu Playbook, the repository spec that holds an organization's tasks in Kadu. - [Kadu Playbook](https://docs.kadu.ia.br/en/playbook.md): The Kadu Playbook, the repository spec that holds an organization's tasks in Kadu. #### agendamento-endpoint-executor The schedules, endpoint, executor and metadata fields of the task block. - [Schedule, endpoint and executor](https://docs.kadu.ia.br/en/playbook/agendamento-endpoint-executor.md): The schedules, endpoint, executor and metadata fields of the task block. #### bloco-task The fields that turn a skill into a task, with the real effect of each one. - [The task: block](https://docs.kadu.ia.br/en/playbook/bloco-task.md): The fields that turn a skill into a task, with the real effect of each one. #### boas-praticas How to write skills that work with nobody watching, and how to maintain the Playbook. - [Best practices](https://docs.kadu.ia.br/en/playbook/boas-praticas.md): How to write skills that work with nobody watching, and how to maintain the Playbook. #### checklist What to check on a new or changed task before publishing. - [Checklist before merging](https://docs.kadu.ia.br/en/playbook/checklist.md): What to check on a new or changed task before publishing. #### conectar Kadu's GitHub App, the deploy key and what Kadu stores about the Playbook. - [Connect the Playbook](https://docs.kadu.ia.br/en/playbook/conectar.md): Kadu's GitHub App, the deploy key and what Kadu stores about the Playbook. #### corpo-da-tarefa Console instrumentation, inputs, the absence of a user and the final result. - [The task body](https://docs.kadu.ia.br/en/playbook/corpo-da-tarefa.md): Console instrumentation, inputs, the absence of a user and the final result. #### dependencias System packages and databases Kadu installs before the execution, and the list of what is available. - [Task dependencies](https://docs.kadu.ia.br/en/playbook/dependencias.md): System packages and databases Kadu installs before the execution, and the list of what is available. #### desenvolvimento-local Run skills in Claude Code in simulation mode, test the CI path and the cycle up to production. - [Local development](https://docs.kadu.ia.br/en/playbook/desenvolvimento-local.md): Run skills in Claude Code in simulation mode, test the CI path and the cycle up to production. #### erros-comuns Symptom, cause and solution for the most frequent problems when maintaining the Playbook. - [Common errors](https://docs.kadu.ia.br/en/playbook/erros-comuns.md): Symptom, cause and solution for the most frequent problems when maintaining the Playbook. #### estrutura The catalog at the root, one plugin per workspace and one folder per skill. - [Playbook structure](https://docs.kadu.ia.br/en/playbook/estrutura.md): The catalog at the root, one plugin per workspace and one folder per skill. #### kadu-yaml Environments, webhooks and organization metadata declared in the repository. - [The kadu.yaml](https://docs.kadu.ia.br/en/playbook/kadu-yaml.md): Environments, webhooks and organization metadata declared in the repository. #### secrets Declare the names in required_secrets, provision the values and leak nothing. - [Secrets in the Playbook](https://docs.kadu.ia.br/en/playbook/secrets.md): Declare the names in required_secrets, provision the values and leak nothing. #### sincronizacao How a push becomes a task in the console, and why code and catalog run on different clocks. - [From merge to console](https://docs.kadu.ia.br/en/playbook/sincronizacao.md): How a push becomes a task in the console, and why code and catalog run on different clocks. #### workflow-proprio Tasks that depend on tools, a fixed schedule or an egress IP that the default runner does not have. - [When the task needs its own workflow](https://docs.kadu.ia.br/en/playbook/workflow-proprio.md): Tasks that depend on tools, a fixed schedule or an egress IP that the default runner does not have. ### primeiros-passos From first sign-in to your first task execution. - [Getting started](https://docs.kadu.ia.br/en/primeiros-passos.md): From first sign-in to your first task execution. --- # Full Documentation Content [Skip to main content](#__docusaurus_skipToContent_fallback) [![Kobana](/en/img/kobana-logo-hor-positivo.png)![Kobana](/en/img/kobana-logo-hor-negativo.png)](https://docs.kadu.ia.br/en/en/.md) [**Kadu**](https://docs.kadu.ia.br/en/en/.md) Documentation [Documentation](https://docs.kadu.ia.br/en/en/.md)[Playbook](https://docs.kadu.ia.br/en/en/playbook.md)[API](https://docs.kadu.ia.br/en/en/api/overview/introduction.md)[MCP](https://docs.kadu.ia.br/en/en/mcp/overview/introduction.md)[CLI](https://docs.kadu.ia.br/en/en/cli/overview/introduction.md) Ask AI [](#) * [🇧🇷 Português](https://docs.kadu.ia.br/en/pt/search) * [🇺🇸 English](https://docs.kadu.ia.br/en/en/search.md) * [🇪🇸 Español](https://docs.kadu.ia.br/en/es/search) Search # Search the documentation ![Empresa Neutra em Carbono](/en/img/badge-neutra-carbono.webp)![Empresa Parceira da Natureza](/en/img/badge-parceira-natureza.webp) Comercial[3003-0386](tel:+551130030386) [](https://wa.me/551130030386)[](mailto:comercial@kobana.com.br) ![Kobana](/en/img/kobana-logo-site.webp)![](/en/img/kobana-cegonha.webp) © 2008/2026 Kobana Tecnologia Ltda. | CNPJ: 05.813.794/0001-26 | Calçada das Margaridas, 163, Sala 02 — Centro Comercial Alphaville — Barueri, SP — 06453-038 --- # API reference --- # Authentication The Kadu API uses a **Kobana Account access token** to authenticate every request, in the `Authorization: Bearer` header, like Kobana's other APIs. ``` curl -H "Authorization: Bearer $KOBANA_TOKEN" \ -H "Content-Type: application/json" \ https://api.kadu.ia.br/v1/me ``` ## The token[​](#the-token "Direct link to The token") The token is an **access token issued by Kobana's Authorization Server** (`account.kobana.com.br`). Kadu only **verifies** it: it keeps no signing key and cannot issue any token. * **Algorithm:** `RS256`, and only `RS256`. * **Type:** the token header must have `typ: at+jwt` (RFC 9068). Another JWT from the same issuer, such as an ID token, is rejected. * **Key:** public, and **discovered, not configured**. The issuer identifier is a URL, and the keys endpoint (`jwks_uri`) is read from the RFC 8414 metadata document published under it (`/.well-known/oauth-authorization-server`). When the Authorization Server rotates the key, Kadu follows on its own. * **When the Authorization Server is called:** never to verify a token, because signature checking is local. What comes from it is the *key set*, which is cached. A fetch happens only with an empty cache, with the cached set older than an hour, or when a token arrives with a `kid` that is not in the cache (a rotation; at most one new fetch every 30 seconds). Each fetch has a 5-second timeout. ### Claims[​](#claims "Direct link to Claims") | Claim | Description | | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `iss` | The Authorization Server's identifier, as a URL: `https://account[.{env}].kobana.com.br`. | | `aud` | This API, as a URL: `https://api.kadu.ia.br` (staging: `https://api.staging.kadu.ia.br`). Can be a **list**: the rule is *contains*, not equality, so a token can address more than one Kobana API. | | `exp` | **Required.** A token with no expiration is rejected. | | `org` | **The organization** on whose behalf the token acts, and it is **required**. It scopes every response. A token without `org` is rejected, instead of falling back to `sub`. | | `sub` | **The person**, never the organization. Goes to auditing. | | `email` | When present, ties the token to a person in the organization, and [their role](https://docs.kadu.ia.br/en/en/api/overview/permissions.md) applies too. An integration token, with no person, is governed by scopes only. | | `jti` | Unique token id, used for revocation and for the [rate limit](https://docs.kadu.ia.br/en/en/api/overview/rate-limit.md). | | `scope` | Space-separated scopes (below). | :::info Check the environment Production's `aud` is not valid in staging, and vice versa. Do not reuse the token across environments. ::: ### Test the token[​](#test-the-token "Direct link to Test the token") `GET /v1/me` tells you who is behind the token. It is the only route without a scope check, precisely so you can test a new token. ## Scopes[​](#scopes "Direct link to Scopes") Scopes have two orthogonal axes: the **resource**, always prefixed by the app (`kadu/tasks`), and the **access level**, a global scope with no prefix: `read`, `write`, `delete`, `import`, `export` or `all`. **A resource without a verb means read.** Everything else requires both: the resource scope *and* the level. A route's resource is the first segment after `/v1`: `/v1/executions/{id}/logs` belongs to the `executions` resource. | Scope | Grants read access to | | ------------------------- | ------------------------------------------------------------------------------------ | | `kadu/organizations` | the token's organization | | `kadu/workspaces` | workspaces | | `kadu/activities` | activities | | `kadu/tasks` | tasks, the run pre-check and the run endpoint (the run itself is `write`) | | `kadu/executions` | executions, logs, events, pending items and search (cancel and continue are `write`) | | `kadu/todos` | pending items (resolving is `write`) | | `kadu/files` | input and output files | | `kadu/environments` | environments | | `kadu/secrets` | secrets: key, notes and metadata. The **value is never returned** by any endpoint | | `kadu/schedules` | schedules | | `kadu/memories` | memories | | `kadu/webhooks` | webhooks (rotating the signing secret is `write`) | | `kadu/webhook_deliveries` | webhook deliveries (resending is `write`) | | `kadu/repository` | the Playbook (syncing is `write`) | | `kadu/usage` | usage and cost | | `kadu/users` | organization members | | `kadu/memberships` | links between people and the organization | | `kadu/roles` | roles | | `kadu/system_events` | system events | | `kadu/all` | wildcard of the resource axis: reads all the resources above and **grants no level** | | Access level | Grants | | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | | `read` | Read. The resource scope already implies it; on a token with no `kadu/…` scope, reads all resources | | `write` | Every `POST`, `PUT` and `PATCH` on the resources the token carries: create, update, run, cancel, sync, rotate secret, resend delivery. Does **not** delete | | `delete` | Every `DELETE` on the resources the token carries, and nothing else | | `import` | Bulk import. No endpoint in this API imports in bulk today | | `export` | Bulk export. No endpoint in this API exports in bulk today | | `all` | All the levels above | ### Which level each endpoint requires[​](#which-level-each-endpoint-requires "Direct link to Which level each endpoint requires") One level per request, determined by the HTTP method. No level substitutes for another: `write` does not delete, and `delete` does not write. | Request | Requires | | ---------------------- | ------------------------------------------------------------------------------ | | `GET` | The resource scope (`kadu/tasks`) | | `POST`, `PUT`, `PATCH` | The resource scope + `write` (`kadu/tasks write`) | | `DELETE` | The resource scope + `delete` (`kadu/tasks delete`); `write` is **not** enough | | `GET /v1/me` | Any valid token, with no scope check | Every deletion is a `DELETE` (the resource gets `deleted_at`) and every `DELETE` is a deletion. Three examples: `kadu/executions` reads executions and does not cancel; `kadu/tasks write` also creates, updates and runs tasks, but does not delete them; `kadu/tasks write delete` does all three. None of them reaches secrets. The granular form `kadu/tasks:write` (or `kadu/tasks:delete`) is an exception to the grammar, not the default format: it satisfies both axes of that resource on its own and exists for "reads one resource, writes another". `kadu/*` is a registration wildcard and is never issued in a token: the comparison here is always literal. A token that carries `kadu/…` scopes is limited to the resources they name, and the levels act only within them. A token with no `kadu/…` scope is limited by level: it reads with `read`, `write` or `all`; writes with `write` or `all`; deletes with `delete` or `all`. A token with neither reaches nothing beyond `GET /v1/me`. Insufficient scope responds `403 INSUFFICIENT_SCOPE`, with the levels that would suffice in `details.required` and the token's scopes in `details.granted`. ## Validation pipeline[​](#validation-pipeline "Direct link to Validation pipeline") In this order: 1. Extracts `Authorization: Bearer `. 2. Verifies the signature with the Authorization Server's public key, accepting only `RS256` and `typ: at+jwt`. 3. Validates `iss`, that `aud` **contains** this API, and that `exp` is present and in the future. 4. Checks whether the `jti` has been revoked. 5. Resolves the organization from `org`. 6. Applies the token's rate limit. 7. Checks the scope against the endpoint. 8. With a person in the token, checks their role in the organization ([Permissions](https://docs.kadu.ia.br/en/en/api/overview/permissions.md)). ## Isolation between organizations[​](#isolation-between-organizations "Direct link to Isolation between organizations") Every query is filtered by the `org` organization. There is no way to point to another organization through the body or the query. A resource from another organization responds `404 NOT_FOUND`, not `403`, so as not to reveal that it exists. ## Failure modes[​](#failure-modes "Direct link to Failure modes") | HTTP | `code` | Cause | | ------- | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 401 | `INVALID_TOKEN` | JWT missing, malformed, with an invalid signature or with another `typ` | | 401 | `INVALID_AUDIENCE` | `aud` from another app or another environment | | 401 | `INVALID_ISSUER` | issuer different from Kobana's Authorization Server | | 401 | `TOKEN_EXPIRED` | `exp` in the past | | 401 | `TOKEN_REVOKED` | revoked `jti` | | 403 | `UNKNOWN_ORGANIZATION` | the `org` points to an organization Kadu does not know | | 403 | `INSUFFICIENT_SCOPE` | token without the required scope | | 403 | `USER_PERMISSION_DENIED`, `NOT_A_MEMBER` | the role of the token's person does not allow it, or they do not belong to the organization ([Permissions](https://docs.kadu.ia.br/en/en/api/overview/permissions.md)) | | 429 | `RATE_LIMITED` | token limit exceeded ([Limits](https://docs.kadu.ia.br/en/en/api/overview/rate-limit.md)) | | **503** | `AS_UNAVAILABLE` | the signing keys could not be fetched from the Authorization Server. Verification itself is local, so this only happens at the moments listed in *When the Authorization Server is called*. It is not a problem with your token: try again after `Retry-After`. It is a `5xx` on purpose: a `401` would send you to debug a good credential during an outage on our side. | See [Errors](https://docs.kadu.ia.br/en/en/api/overview/errors.md) for the full envelope. ## Best practices[​](#best-practices "Direct link to Best practices") * **Never** commit a token to git. Use environment variables or a vault (AWS Secrets Manager, Vault…). * Use one token per integration: you can revoke one without taking down the others. * Ask for the minimum scopes: `kadu/executions` for whoever only follows, `kadu/tasks write` for whoever runs, `delete` only for whoever deletes. * Revoke any leaked token immediately. --- # Conventions ## Format[​](#format "Direct link to Format") The API accepts and responds with `JSON`. Send `Content-Type: application/json` on every request with a body. | Type | Format | | -------------- | ------------------------------------------------------------------------------------------ | | Field names | `snake_case`, in both directions | | Date and time | ISO 8601 in UTC: `2026-10-31T12:00:00.000Z` | | Date | `YYYY-MM-DD` (for example, an execution's `reference_date`) | | Identifiers | UUID | | Cost | `number` in dollars, in fields ending in `_usd` | | Price | `number` in reais, in fields ending in `_brl` | | Free-form maps | `custom_metadata`, `payload`, `parameters`… are opaque: keys come back as they were stored | ## Envelope[​](#envelope "Direct link to Envelope") Success for a resource: ``` { "data": { "id": "2f1c…", "label": "Baixar pagamentos" } } ``` Success for a listing: ``` { "data": [ { "id": "2f1c…" }, { "id": "8a40…" } ], "meta": { "next_cursor": "eyJpZCI6…", "limit": 25 } } ``` Error: ``` { "error": { "code": "VALIDATION_ERROR", "message": "sort_order must be asc or desc", "details": {} } } ``` `code` is stable, in `UPPER_SNAKE_CASE`: it is what your code should compare, not `message`. See [Errors](https://docs.kadu.ia.br/en/en/api/overview/errors.md). ## Write bodies[​](#write-bodies "Direct link to Write bodies") * `POST`, `PUT` and `PATCH` accept a body of up to **1 MiB**. Above that, the response is `413 PAYLOAD_TOO_LARGE`. * `PATCH` changes only the fields sent. * Every write accepts `X-Idempotency-Key` ([Idempotency](https://docs.kadu.ia.br/en/en/api/overview/idempotency.md)). ## `metadata` and `custom_metadata`[​](#metadata-and-custom_metadata "Direct link to metadata-and-custom_metadata") `custom_metadata` is yours: a free-form object that you write and read on organization, workspace, activity, task and memory. `system_metadata` belongs to Kadu and is read-only. ## Removal[​](#removal "Direct link to Removal") `DELETE` does not erase the row: the resource gets `deleted_at` and disappears from reads. Removing the same resource again responds `404`. ## Running a task[​](#running-a-task "Direct link to Running a task") `POST /v1/tasks/{id}/run` responds `201` with the id of the created execution: `{ "data": { "execution_id": "…", "status": "queued" } }`. The execution joins the task's queue and starts on its own ([Executions](https://docs.kadu.ia.br/en/en/conceitos/execucoes.md)). Follow it through `GET /v1/executions/{id}`, its `/events` and `/logs`, or the [webhooks](https://docs.kadu.ia.br/en/en/api/overview/webhooks.md). Before running, `GET /v1/tasks/{id}/readiness?environment_id=…` tells you whether the environment has all the secrets the task requires, without creating anything. --- # Data Retention This page describes how long Kadu keeps each type of data. ## Data with a deadline[​](#data-with-a-deadline "Direct link to Data with a deadline") These records are permanently deleted after **90 days**, every day, by a retention job: | Data type | Where it shows up | Time | | ------------------ | --------------------------------- | ------- | | Execution events | `GET /v1/executions/{id}/events` | 90 days | | Execution logs | `GET /v1/executions/{id}/logs` | 90 days | | System events | `GET /v1/system_events` | 90 days | | Webhook deliveries | `GET /v1/webhook_deliveries` | 90 days | | Audit logs | the organization's internal trail | 90 days | After the deadline, the event or log of an old execution no longer exists and the API answers with an empty list for it. If you need to keep it longer, export what matters beforehand. ## Other data[​](#other-data "Direct link to Other data") Executions, tasks, workspaces, activities, environments, secrets, schedules, webhooks, memories and files are kept until you remove them. **Removal is logical** (soft delete): the resource stops showing up in the API, but the record remains. The times above may change at any time. --- # Endpoints ## Platform (Agent Console)[​](#platform-agent-console "Direct link to Platform (Agent Console)") | Environment | URL | | ----------- | -------------------------------- | | Staging | | | Production | | ## API (v1)[​](#api-v1 "Direct link to API (v1)") | Environment | URL | | ----------- | ----------------------------------- | | Staging | `https://api.staging.kadu.ia.br/v1` | | Production | `https://api.kadu.ia.br/v1` | ## MCP server[​](#mcp-server "Direct link to MCP server") | Environment | URL | | ----------- | -------------------------------- | | Staging | `https://mcp.staging.kadu.ia.br` | | Production | `https://mcp.kadu.ia.br` | See [MCP server](https://docs.kadu.ia.br/en/en/mcp/overview/introduction.md). ## Documentation[​](#documentation "Direct link to Documentation") | Environment | URL | | ------------- | ------------------------- | | Documentation | | ## Sandbox[​](#sandbox "Direct link to Sandbox") Kadu has no sandbox host. To test without affecting production, use a separate **environment** (for example, "Sandbox"): each secret and each schedule belongs to an environment, and you choose the environment when triggering a task. See [Environments](https://docs.kadu.ia.br/en/en/configuracoes/ambientes.md). --- # Errors Every v1 API error comes in the same envelope: ``` { "error": { "code": "INSUFFICIENT_SCOPE", "message": "This token was not granted write access to this resource.", "details": { "required": ["write", "all"], "granted": ["read"] } } } ``` Compare `code`, which is stable. `message` is for people and may change. `details` appears when there is something to detail. ## Codes[​](#codes "Direct link to Codes") | Status | `code` | When | | ------ | --------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | | 400 | `VALIDATION_ERROR` | invalid body, filter, sort or cursor | | 401 | `INVALID_TOKEN`, `INVALID_AUDIENCE`, `INVALID_ISSUER`, `TOKEN_EXPIRED`, `TOKEN_REVOKED` | access token problem ([Authentication](https://docs.kadu.ia.br/en/en/api/overview/authentication.md)) | | 403 | `UNKNOWN_ORGANIZATION` | the token's organization is not in Kadu | | 403 | `INSUFFICIENT_SCOPE` | the token lacks the route's scope ([Permissions](https://docs.kadu.ia.br/en/en/api/overview/permissions.md)) | | 403 | `USER_PERMISSION_DENIED`, `NOT_A_MEMBER` | the role of the token's person does not allow it | | 403 | `FEATURE_NOT_ENABLED` | Kobana has not enabled this operation for the organization | | 403 | `ENTITLEMENT_REQUIRED` | run without Kadu in the organization's plan | | 404 | `NOT_FOUND` | the resource does not exist, was removed or belongs to another organization | | 409 | `CONFLICT` | state conflict (for example, environment name already in use) | | 409 | `IDEMPOTENCY_KEY_REUSED`, `IDEMPOTENCY_REQUEST_IN_PROGRESS` | see [Idempotency](https://docs.kadu.ia.br/en/en/api/overview/idempotency.md) | | 413 | `PAYLOAD_TOO_LARGE` | body above 1 MiB | | 422 | `MISSING_SECRETS` | run with required secrets that have no value in the environment | | 422 | `ENVIRONMENT_REQUIRED` | run without a valid environment | | 422 | `COST_LIMIT_REACHED` | the organization reached the monthly cost cap | | 429 | `RATE_LIMITED` | request limit ([Limits](https://docs.kadu.ia.br/en/en/api/overview/rate-limit.md)) | | 500 | `INTERNAL_ERROR` | our failure. The message never carries internal details. | | 503 | `AS_UNAVAILABLE` | token verification is unavailable. Try again after `Retry-After`. | Besides these, a route may return its own code, documented in its reference. ## What to do[​](#what-to-do "Direct link to What to do") | Status | Retry? | | ------------------------------------- | --------------------------------------------------------------------- | | 4xx | No: the same request will fail again. Fix the cause. | | 409 `IDEMPOTENCY_REQUEST_IN_PROGRESS` | Yes, after a few seconds, with the same key. | | 429 | Yes, after `Retry-After`. | | 5xx | Yes, with increasing wait and the same `X-Idempotency-Key` on writes. | The [run endpoint](https://docs.kadu.ia.br/en/en/api/overview/run-task-endpoint.md) uses the same envelope, with the lowercase codes listed on its page. --- # Idempotency Every write (`POST`, `PUT`, `PATCH`) accepts the `X-Idempotency-Key` header. With it, repeating the same request (because of a timeout, a network drop, an automatic retry) **does not create anything twice**: the API returns the original response. ``` curl -H "Authorization: Bearer $KOBANA_TOKEN" \ -H "Content-Type: application/json" \ -H "X-Idempotency-Key: run-2026-10-31-settlement" \ -X POST https://api.kadu.ia.br/v1/tasks/2f1c…/run \ -d '{"environment_id":"8a40…","reference_date":"2026-10-30"}' ``` ## Rules[​](#rules "Direct link to Rules") | Rule | Value | | --------------------------- | -------------------------------------------------------------------------- | | Key format | opaque text, 1 to 255 characters | | Validity | 24 hours | | Scope | the organization: the same key in different organizations does not collide | | What identifies the request | method, path, query and body | | Situation | Response | | -------------------------------------------- | ------------------------------------------------------------------------- | | New key | the request runs normally, and the response is stored | | Same key, same request | the original response (status and body), with `X-Idempotent-Replay: true` | | Same key, different request | `409 IDEMPOTENCY_KEY_REUSED` | | Same key while the first is still processing | `409 IDEMPOTENCY_REQUEST_IN_PROGRESS` | Generate one key per **business operation** (for example, `run--`), not per attempt: using the same key on every retry is what makes the retry safe. The [run endpoint](https://docs.kadu.ia.br/en/en/api/overview/run-task-endpoint.md) accepts the same header, with the same rules. --- # Introduction The **Kadu v1 API** gives another system everything the Agent Console does: list and run tasks, follow executions (logs, events, pending items, files), configure environments, secrets, schedules and webhooks, sync the Playbook, and query usage and cost. ## Addresses[​](#addresses "Direct link to Addresses") | Environment | Base URL | | ----------- | ----------------------------------- | | Production | `https://api.kadu.ia.br/v1` | | Staging | `https://api.staging.kadu.ia.br/v1` | ``` curl -H "Authorization: Bearer $KOBANA_TOKEN" \ https://api.kadu.ia.br/v1/me ``` Every route requires a Kobana Account **access token** in the `Authorization: Bearer` header. See [Authentication](https://docs.kadu.ia.br/en/en/api/overview/authentication.md). ## What you can do[​](#what-you-can-do "Direct link to What you can do") | Resource | Endpoints | What for | | ------------- | ----------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- | | Identity | `/v1/me` | check whose token it is | | Organization | `/v1/organizations` | read and change the token's organization | | Structure | `/v1/workspaces`, `/v1/activities`, `/v1/tasks` | the task tree | | Run | `POST /v1/tasks/{id}/run`, `GET /v1/tasks/{id}/readiness` | run a task and check beforehand whether the environment is ready | | Executions | `/v1/executions`, `/logs`, `/events`, `/todos`, `/cancel`, `/continue`, `/v1/executions/search` | follow, cancel, continue a conversational task and search | | Pending items | `PATCH /v1/todos/{id}` | resolve a pending item | | Files | `/v1/files` | upload input files and download output files | | Configuration | `/v1/environments`, `/v1/secrets`, `/v1/schedules`, `/v1/tasks/{id}/endpoint` | environments, credentials, schedules and the run endpoint | | Integrations | `/v1/webhooks`, `/v1/webhook_deliveries` | webhooks, deliveries and resend | | Repository | `/v1/repository`, `POST /v1/repository/sync` | state and sync of the Playbook | | Usage | `/v1/usage` | tokens, cost and price per period | | People | `/v1/users`, `/v1/memberships`, `/v1/roles` | members, memberships and roles | | Memory | `/v1/memories` | records the organization keeps per entity | | Events | `/v1/system_events` | organization events | The full reference for each endpoint is in [**Resources**](https://docs.kadu.ia.br/en/en/api/v1), generated from the OpenAPI specification ([Specification](https://docs.kadu.ia.br/en/en/api/overview/openapi.md)). ## The contract on one screen[​](#the-contract-on-one-screen "Direct link to The contract on one screen") * JSON in `snake_case` in both directions. * Success: `{ "data": … }` or `{ "data": […], "meta": { "next_cursor", "limit" } }`. * Error: `{ "error": { "code", "message", "details" } }`, with `code` in `UPPER_SNAKE_CASE`. * Cursor-based listings ([Pagination](https://docs.kadu.ia.br/en/en/api/overview/pagination.md)). * `X-Idempotency-Key` on every `POST`, `PUT` and `PATCH` ([Idempotency](https://docs.kadu.ia.br/en/en/api/overview/idempotency.md)). * 600 requests per minute per token ([Limits](https://docs.kadu.ia.br/en/en/api/overview/rate-limit.md)). Details in [Conventions](https://docs.kadu.ia.br/en/en/api/overview/conventions.md). ## From the terminal[​](#from-the-terminal "Direct link to From the terminal") The [`kobana` CLI](https://docs.kadu.ia.br/en/en/cli/overview/introduction.md) calls every one of these endpoints from the command line (`kobana kadu tasks run`, `kobana kadu executions logs`), with the same token. ## Running without the API[​](#running-without-the-api "Direct link to Running without the API") For a system to run **one specific task** without a Kobana Account token, use the [run endpoint](https://docs.kadu.ia.br/en/en/api/overview/run-task-endpoint.md): one URL per task, with its own credential. --- # OpenAPI API v1 is described by an **OpenAPI 3.1** specification, generated from Kadu's code: the schemas that validate each request are the same ones that document it. The reference under **Resources** is generated from it. ## OpenAPI 3.1[​](#openapi-31 "Direct link to OpenAPI 3.1") | Version | Language | File | | ------- | --------- | ------------------------------------------------------------------------------ | | v1 | Português | [openapi-v1.yaml](https://docs.kadu.ia.br/en/en/openapi/openapi-v1.yaml) | | v1 | English | [openapi-v1-en.yaml](https://docs.kadu.ia.br/en/en/openapi/openapi-v1-en.yaml) | | v1 | Español | [openapi-v1-es.yaml](https://docs.kadu.ia.br/en/en/openapi/openapi-v1-es.yaml) | Use the file to generate a client in your language, import it into an HTTP client (Postman, Insomnia, Bruno) or validate your requests in tests. The specification's paths are already at the public address (`https://api.kadu.ia.br/v1/...`). Configure the Kobana Account token as a Bearer in the tool ([Authentication](https://docs.kadu.ia.br/en/en/api/overview/authentication.md)). --- # Pagination, sorting and filters ## Cursor[​](#cursor "Direct link to Cursor") Listings are paginated by **cursor**: | Parameter | Default | Rule | | --------- | ------- | -------------------------------------- | | `limit` | `25` | from 1 to 100 | | `cursor` | — | the previous page's `meta.next_cursor` | ``` curl -H "Authorization: Bearer $KOBANA_TOKEN" \ "https://api.kadu.ia.br/v1/executions?limit=50" ``` ``` { "data": [ … ], "meta": { "next_cursor": "eyJpZCI6IjJmMWMi…", "limit": 50 } } ``` Repeat the call with `cursor=` until `next_cursor` comes back `null`. The cursor is opaque: do not build or interpret it. An invalid cursor responds `400 VALIDATION_ERROR`. ## Sorting[​](#sorting "Direct link to Sorting") | Parameter | Default | Values | | ------------ | ------------ | ---------------------------------------------------------------- | | `sort_by` | `created_at` | `created_at`, `updated_at` or a filterable field of the resource | | `sort_order` | `desc` | `asc` or `desc` | ## Filters[​](#filters "Direct link to Filters") Each resource accepts filters on specific fields, in the form `field=value` or `field[operator]=value`: | Operator | Example | Meaning | | ------------------------ | -------------------------------- | -------------------------------------------------------- | | (none) | `status=failed` | equal | | `in` | `status[in]=failed,cancelled` | one of the values, comma-separated | | `gte`, `lte`, `gt`, `lt` | `reference_date[gte]=2026-10-01` | greater than or equal, less than or equal, greater, less | | `not` | `status[not]=success` | different | Examples of filterable fields: | Resource | Fields | | ---------------- | ----------------------------------------------------------------------------------------------------------------- | | `/v1/tasks` | `workspace_id`, `activity_id`, `skill_slug` (equal or `in`) | | `/v1/executions` | `task_id`, `workspace_id`, `activity_id`, `status` (equal or `in`); `reference_date`, `started_at` (`gte`, `lte`) | An operator that the field does not accept responds `400 VALIDATION_ERROR`. A parameter that is not a filter of the resource is ignored. ``` curl -H "Authorization: Bearer $KOBANA_TOKEN" \ "https://api.kadu.ia.br/v1/executions?status[in]=failed,cancelled&reference_date[gte]=2026-10-01&sort_order=asc" ``` ## Lists without a cursor[​](#lists-without-a-cursor "Direct link to Lists without a cursor") Some listings return a fixed set, with no pagination: webhook deliveries (the 50 most recent), organization members (up to 100) and system events (the 100 most recent). Each endpoint's reference says which case applies. --- # Permissions An authenticated request goes through three checks, in this order. Any one that denies ends the request with `403`. | # | Check | What it verifies | Rejection | | - | --------------------------- | ------------------------------------------------------------------------- | -------------------------------------------------- | | 1 | **Token scope** | the resource and access level granted to the token | `403 INSUFFICIENT_SCOPE` | | 2 | **Person's role** | what the token's person can do in the organization | `403 USER_PERMISSION_DENIED` or `403 NOT_A_MEMBER` | | 3 | **Organization permission** | whether Kobana enabled the equivalent console screen for the organization | `403 FEATURE_NOT_ENABLED` | ## 1. Scopes[​](#1-scopes "Direct link to 1. Scopes") The token needs the resource scope and the access level that the method requires. The grammar, the resource table and the examples are in [Authentication → Scopes](https://docs.kadu.ia.br/en/en/api/overview/authentication.md#scopes). ## 2. Person's role[​](#2-persons-role "Direct link to 2. Person's role") A token issued for a person (with the `email` claim) **never reaches more than the person can**: besides the scopes, their role in the organization applies. Each route requires an `agent..` permission: | Action | When | | ---------------------------- | ------------------------- | | `list`, `read` | list and get details | | `create`, `update`, `delete` | create, update and remove | | `executions.run` | run a task | | `webhook_deliveries.replay` | resend a webhook delivery | | `repository.sync` | sync the Playbook | Roles accept wildcards: `*`, `agent.*` and `agent..*`. | Response | When | | ---------------------------- | ---------------------------------------------------------------------- | | `403 USER_PERMISSION_DENIED` | the role does not grant the permission (`details.required` says which) | | `403 NOT_A_MEMBER` | the token's person has no membership in the organization | An **integration token**, with no person, is governed by scopes only. `GET /v1/me` does not check the role. ## 3. Organization permission[​](#3-organization-permission "Direct link to 3. Organization permission") The routes that mirror console screens (run, logs, webhooks, deliveries, environments, secrets, schedules, task endpoint, repository and usage) also respect the permissions Kobana grants the organization. An operation that is not enabled responds `403 FEATURE_NOT_ENABLED`, with `details.permission`. ## Contracted plan[​](#contracted-plan "Direct link to Contracted plan") Running a task requires Kadu to be in the organization's plan. Without a plan, the run responds `403 ENTITLEMENT_REQUIRED`, with `details.reason` and `details.required_product`. Reading, registration and cancellation are never blocked by the plan. See [Plan and costs](https://docs.kadu.ia.br/en/en/conceitos/plano-e-custos.md). --- # Rate limits | Where | Limit | Per | | ---------------------------------------------- | -------------------------------------------------------- | ------------------ | | v1 API | 600 requests per minute | token (`jti`) | | Execution search (`GET /v1/executions/search`) | 30 requests per minute, in addition to the general limit | token | | Run endpoint (`POST /run-task/{task_id}`) | 60 requests per minute | source IP and task | The API limit is **per token**, not per organization: one integration's runaway loop does not consume the others' quota. ## Headers[​](#headers "Direct link to Headers") Every API response carries the window state: | Header | Content | | ----------------------- | ----------------------------------------- | | `X-RateLimit-Limit` | the window's limit | | `X-RateLimit-Remaining` | how many requests remain | | `X-RateLimit-Reset` | when the window restarts, in Unix seconds | Once the limit is exceeded, the response is `429 RATE_LIMITED`, with `Retry-After` in seconds. Wait that long before trying again. --- # Run endpoint The run endpoint lets another system run **one specific task** with an HTTP `POST`, without a Kobana Account token: each task has its own URL and its own credential. It is the path for an ERP, a form or a third-party webhook to trigger Kadu. ``` POST https://api.kadu.ia.br/run-task/{task_id} ``` Staging: `https://api.staging.kadu.ia.br/run-task/{task_id}`. Each task's URL appears in **Settings → Endpoints**, with a call example. ## Enable[​](#enable "Direct link to Enable") A task's endpoint is enabled by the Playbook (`task.endpoint`), and the credential is registered in Kadu, never in the Playbook. See [Endpoints](https://docs.kadu.ia.br/en/en/configuracoes/endpoints.md) and [Schedule, endpoint and executor](https://docs.kadu.ia.br/en/en/playbook/agendamento-endpoint-executor.md). ## Authentication[​](#authentication "Direct link to Authentication") One of four, according to the task's configuration: | Type | How to send | | ------------- | ------------------------------------------------------------------- | | HTTP Basic | `Authorization: Basic ` | | Bearer Token | `Authorization: Bearer ` | | Header | the configured header, with the configured value (`X-Api-Key: …`) | | URL parameter | `?=` in the URL. Avoid: the URL appears in proxy logs. | ## Body[​](#body "Direct link to Body") ``` curl -X POST "https://api.kadu.ia.br/run-task/2f1c…" \ -H "Authorization: Bearer $TASK_TOKEN" \ -H "Content-Type: application/json" \ -H "X-Idempotency-Key: order-48213" \ -d '{ "environment_id": "8a40…", "reference_date": "2026-10-30", "parameters": { "pedido": "48213" }, "locale": "pt-BR" }' ``` | Field | Required | Rule | | ------------------------- | -------- | ------------------------------------------------------------------------ | | `environment_id` | yes | UUID of the environment the task runs in | | `reference_date` | no | `YYYY-MM-DD`; default: today's date | | `parameters` | no | object validated against the task's `parameters_schema`; default `{}` | | `locale` | no | execution language (`pt-BR`, `en`) | | `additional_instructions` | no | free text sent to the agent along with the skill, up to 8,000 characters | The body is JSON only, up to 1 MiB: a task triggered through the endpoint **does not receive input files**. ## Responses[​](#responses "Direct link to Responses") `201` when the execution was created. It joins the task's queue and starts on its own: ``` { "execution_id": "c7d2…", "status": "queued" } ``` | Response | When | | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | | `400 validation_failed` | body outside the format above | | `401 unauthorized` | credential missing or wrong | | `403 ENTITLEMENT_REQUIRED` | the organization does not have Kadu in its plan | | `404 not_found` | task does not exist, was removed or has the endpoint disabled. The response is the same in all three cases, so as not to reveal whether the task exists. | | `409` | `X-Idempotency-Key` conflict ([Idempotency](https://docs.kadu.ia.br/en/en/api/overview/idempotency.md)) | | `413 payload_too_large` | body above 1 MiB | | `422 unprocessable_entity` | `parameters` did not pass `parameters_schema` (`details` carries the errors) | | `422 environment_required` | `environment_id` missing or invalid | | `422 missing_secrets` | secrets required by the task with no value in the environment (`details` says which) | | `422 cost_limit_reached` | the organization reached the monthly cost cap | | `429 rate_limited` | more than 60 calls per minute from the same IP for the same task | With `X-Idempotency-Key`, repeating the same call returns the original response with `X-Idempotent-Replay: true`, without creating another execution. To know when the execution finished, subscribe to `execution.completed` and `execution.failed` in the [webhooks](https://docs.kadu.ia.br/en/en/api/overview/webhooks.md). ## Autonomous Inbox webhook[​](#autonomous-inbox-webhook "Direct link to Autonomous Inbox webhook") The endpoint directly receives Kobana's [Autonomous Inbox](https://docs.inbox.kobana.com.br) webhook. A delivery with the `X-Kobana-App: inbox` header carries the Inbox envelope in place of the body above, and Kadu converts it: * `data.email` becomes the `parameters`, **filtered by the properties of the task's `parameters_schema`** (with no schema, the whole object goes); * the `delivered_at` date becomes the `reference_date`; * the environment comes from the query, because the Inbox does not let you change the body. Register in the Inbox the URL `https://api.kadu.ia.br/run-task/{task_id}?environment_id=` and configure the Inbox webhook's optional authentication (Basic, Bearer or header) with the task's credential. --- # User-Agent You can include the `User-Agent` header with your application's name and a valid email address, so that we can get in touch if: 1. You are doing something wrong, and we can warn you before blocking; 2. You are doing something really cool, and we can congratulate you 🙂 Example: ``` User-Agent: My Name (myname@example.com) ``` Example of passing this header with cURL: ``` curl -i \ -H "Authorization: Bearer $KOBANA_TOKEN" \ -H 'User-Agent: My Name (myname@example.com)' \ -X GET 'https://api.kadu.ia.br/v1/me' ``` This header is **optional** on every request, but recommended. --- # Webhooks A webhook delivers a signed JSON `POST` to the address you registered when an event happens. Register it through the console ([Settings → Webhooks](https://docs.kadu.ia.br/en/en/configuracoes/webhooks.md)), through the API (`POST /v1/webhooks`) or through [`kadu.yaml`](https://docs.kadu.ia.br/en/en/playbook/kadu-yaml.md). ## Body[​](#body "Direct link to Body") ``` { "event": "execution.completed", "event_id": "5b0e…", "delivered_at": "2026-10-31T12:04:51.210Z", "organization_id": "1d7a…", "workspace_id": "9c3f…", "data": { "…": "the event's resource, in snake_case" } } ``` | Field | What it is | | ----------------- | --------------------------------------------------------------------------------------------------- | | `event` | the event name (list in [Webhooks](https://docs.kadu.ia.br/en/en/configuracoes/webhooks.md#events)) | | `event_id` | event id: the same on all delivery attempts of it | | `delivered_at` | when this delivery went out | | `organization_id` | the organization | | `workspace_id` | the event's workspace, or `null` on an organization event | | `data` | the event's resource. `custom_metadata` and `metadata` go as they were stored. | A webhook with a workspace receives only that workspace's events. A webhook without a workspace receives all of them. ## Headers[​](#headers "Direct link to Headers") | Header | Content | | ------------------------------------------------------ | -------------------------------------------------------------------------------------------------------- | | `X-Kobana-App` | `kadu` | | `X-Kobana-Environment` | `production` or `staging`. Check before processing: a staging delivery must never touch production data. | | `X-Kobana-Webhook-Id` | the webhook that generated the delivery | | `X-Kobana-Event` | the event name | | `X-Kobana-Event-Id` | the event id | | `X-Kobana-Delivery-Id` | this delivery's id | | `X-Kobana-Timestamp` | Unix seconds at which the delivery was signed | | `X-Kobana-Signature` | the signature | | `webhook-id`, `webhook-timestamp`, `webhook-signature` | the same three values, in the [Standard Webhooks](https://www.standardwebhooks.com) layout | | `Authorization` or the configured header | the credential you configured on the webhook (Bearer, Basic or header) | ## Verify the signature[​](#verify-the-signature "Direct link to Verify the signature") The signature follows [Standard Webhooks](https://www.standardwebhooks.com): any compatible verification library works. * **Signing secret:** `whsec_` followed by 32 bytes in base64. The HMAC key is the decoded bytes after the prefix. * **Signed content:** `..`, with `X-Kobana-Delivery-Id` (= `webhook-id`) and `X-Kobana-Timestamp`. * **Header:** `v1,`. During a rotation, the header carries one signature per still-valid secret, separated by spaces. Accept the delivery if **any one** matches. * **Window:** reject a delivery whose timestamp is more than 5 minutes from your clock, to limit replay. ``` import base64, hashlib, hmac, time def verify(secret: str, delivery_id: str, timestamp: str, body: bytes, signature: str) -> bool: if abs(time.time() - int(timestamp)) > 300: return False key = base64.b64decode(secret.removeprefix("whsec_")) content = f"{delivery_id}.{timestamp}.".encode() + body expected = base64.b64encode(hmac.new(key, content, hashlib.sha256).digest()).decode() return any( hmac.compare_digest(token.split(",", 1)[1], expected) for token in signature.split() if token.startswith("v1,") ) ``` Use the **raw body**, before any JSON parsing: reserializing changes the bytes and breaks the signature. :::note Legacy header `X-Kobana-Signature-Sha256` is Kadu's old scheme (`sha256=` of `timestamp.body`) and is **deprecated**. Use `X-Kobana-Signature`. ::: ## Signing secret and rotation[​](#signing-secret-and-rotation "Direct link to Signing secret and rotation") The signing secret appears **once**, when the webhook is created. To change it, use `POST /v1/webhooks/{id}/rotate-secret`: the response carries the new secret, and the previous one keeps signing for 24 hours (`previous_signing_secret_expires_at`), so you can switch the key in the receiver without losing deliveries. ## Deliveries and failures[​](#deliveries-and-failures "Direct link to Deliveries and failures") * Respond `2xx` within 10 seconds and process later. Any other response, or the timeout, counts as a failure. * The same event can arrive more than once: use `event_id` to discard duplicates. * After 5 consecutive failures, the webhook is **deactivated** and the emails registered on it are notified. * `GET /v1/webhook_deliveries` lists the 50 most recent deliveries, with status and response, and `POST /v1/webhook_deliveries/{id}/replay` resends a delivery. * Deliveries are kept for 90 days. The destination URL must be public and `https`. Private, loopback or `http` addresses are rejected. --- # Authentication :::caution Products do not share credentials `KOBANA_TOKEN` holds the credential **of one product at a time**. A Kadu Agent token does not authenticate on any other Kobana product, and vice versa. ::: ## The token[​](#the-token "Direct link to The token") The CLI uses the same **Kobana Account access token** as the API (RS256, with `aud` equal to `https://api.kadu.ia.br`), read from `KOBANA_TOKEN` and sent as `Authorization: Bearer`. The rules for the token, scopes and permissions are the API's: see [API authentication](https://docs.kadu.ia.br/en/en/api/overview/authentication.md) and [Permissions](https://docs.kadu.ia.br/en/en/api/overview/permissions.md). `kobana auth login`, which belongs to the Banking Gateway, does **not** produce this token. ``` export KOBANA_TOKEN= kobana kadu me list ``` `kobana kadu me list` calls `GET /v1/me`, the only route without a scope check: use it to test a new token. Since the variable is the same for every product, if you switch between them in one session, pass the token per command: ``` KOBANA_TOKEN=$TOKEN_KADU kobana kadu executions list ``` The CLI also reads a `.env` file in the folder where you run the command and then the one at `~/.config/kobana/.env` (on macOS, `~/Library/Application Support/kobana/.env`). ## Resolution order[​](#resolution-order "Direct link to Resolution order") | Priority | Method | Setting | | -------- | ----------------- | ------------------------------------------ | | 1 | Direct token | `KOBANA_TOKEN` | | 2 | Credentials file | `KOBANA_CREDENTIALS_FILE` | | 3 | Saved credentials | `kobana auth login` (Banking Gateway only) | ## Scopes[​](#scopes "Direct link to Scopes") Ask only for what the use needs, as in the API: `kadu/executions` for those who only follow runs, `kadu/tasks write` for those who trigger them, `delete` only for those who remove. A command outside the scope answers `403 INSUFFICIENT_SCOPE`, and the CLI exits with code `1`. ## Environments[​](#environments "Direct link to Environments") The CLI works in three environments, and each product has its own hosts. **Production is the default.** | Environment | API | `--env` | | ----------- | -------------------- | ---------------------- | | Production | `api.kadu.ia.br` | `production` (default) | | Development | `localhost:3030/api` | `development` | Kadu has no sandbox: `--env sandbox` ends with an error instead of sending a test to production. The CLI does not point to staging either; for staging, call the API directly (`https://api.staging.kadu.ia.br/v1`). `KOBANA_ENVIRONMENT` sets the default environment. A token from one environment is not valid in another. ``` # production (default) kobana kadu tasks list # development kobana kadu tasks list --env development # KOBANA_ENVIRONMENT export KOBANA_ENVIRONMENT=development kobana kadu tasks list ``` ## Good practices[​](#good-practices "Direct link to Good practices") * **Never** commit the token or the `.env` that holds it. * In an AI agent, hand over a token with minimal scopes and a short life: the CLI does everything the token allows. ## Next steps[​](#next-steps "Direct link to Next steps") * [Usage](https://docs.kadu.ia.br/en/en/cli/usage/overview.md) — syntax, resources, examples. * [Environment variables](https://docs.kadu.ia.br/en/en/cli/overview/environment-variables.md) — the full reference, including configuration and logs. --- # Environment variables Every variable can also be set in a `.env` file (loaded via `dotenvy`). ## Authentication[​](#authentication "Direct link to Authentication") | Variable | Description | | ------------------------- | ------------------------------------------------------------------------ | | `KOBANA_TOKEN` | Token of the product you will call (Bearer). Not shared between products | | `KOBANA_CREDENTIALS_FILE` | Path to an OAuth credentials JSON file | | `KOBANA_CLIENT_ID` | OAuth client ID (Banking Gateway) | | `KOBANA_CLIENT_SECRET` | OAuth client secret (Banking Gateway) | ## Configuration[​](#configuration "Direct link to Configuration") | Variable | Description | | -------------------- | -------------------------------------------------------------- | | `KOBANA_CONFIG_DIR` | Configuration directory (default: `~/.config/kobana`) | | `KOBANA_ENVIRONMENT` | `production` (default) or `development`. Kadu has no `sandbox` | ## Logs[​](#logs "Direct link to Logs") | Variable | Description | | ----------------- | ----------------------------------------------------------------- | | `KOBANA_LOG` | Log level on stderr (e.g. `kobana=debug`). Off by default. | | `KOBANA_LOG_FILE` | Directory for JSON log files with daily rotation. Off by default. | ## Next steps[​](#next-steps "Direct link to Next steps") * [Authentication](https://docs.kadu.ia.br/en/en/cli/overview/authentication.md) — resolution order and per-product credentials. * [Exit codes](https://docs.kadu.ia.br/en/en/cli/overview/exit-codes.md) — process exit codes, for scripts. --- # Exit codes Every `kobana` run ends with one of these codes, so a script or agent can decide what to do without parsing the message. | Code | Meaning | | ---- | --------------------------------------------------------------------------------------------- | | `0` | Success | | `1` | API error (`4xx`/`5xx`) or network error | | `2` | Missing token, or a command and options the CLI does not recognize | | `3` | Invalid input (`--params` or `--json` that is not JSON, identifier with forbidden characters) | | `4` | Unknown `kobana schema` path | | `5` | Internal error | An error also comes out as JSON on stdout. For an API error, `error.code` is the HTTP status and `error.details` carries the API response, with its [error envelope](https://docs.kadu.ia.br/en/en/api/overview/errors.md) (`details.error.code`, such as `INSUFFICIENT_SCOPE`): ``` { "error": { "code": 403, "message": "…", "details": { "error": { "code": "INSUFFICIENT_SCOPE", "message": "…", "details": { "required": ["write", "all"] } } } } } ``` ## Next steps[​](#next-steps "Direct link to Next steps") * [Usage](https://docs.kadu.ia.br/en/en/cli/usage/overview.md) — syntax, resources, examples. * [Global flags](https://docs.kadu.ia.br/en/en/cli/usage/global-flags.md) — `--dry-run`, `--output-format` and the rest. --- # Installation ### Script (macOS and Linux)[​](#script-macos-and-linux "Direct link to Script (macOS and Linux)") Downloads the latest binary to `~/.kobana/bin`, checks the SHA-256 published on GitHub and creates the `~/.local/bin/kobana` link: ``` curl -fsSL https://raw.githubusercontent.com/universokobana/kobana-cli/main/scripts/install.sh | bash ``` To update, use `kobana update`. ### Homebrew (macOS / Linux)[​](#homebrew-macos--linux "Direct link to Homebrew (macOS / Linux)") ``` brew tap universokobana/tap brew install kobana ``` To update: ``` brew update && brew upgrade kobana ``` ### Prebuilt binaries[​](#prebuilt-binaries "Direct link to Prebuilt binaries") Download the binary for your platform from the [Releases page](https://github.com/universokobana/kobana-cli/releases/latest) and put it on your `PATH`. | Platform | File | | ------------------- | -------------------------- | | Linux x86\_64 | `kobana-linux-amd64` | | Linux ARM64 | `kobana-linux-arm64` | | macOS Intel | `kobana-darwin-amd64` | | macOS Apple Silicon | `kobana-darwin-arm64` | | Windows x86\_64 | `kobana-windows-amd64.exe` | ### Nix[​](#nix "Direct link to Nix") ``` # Run straight from GitHub nix run github:universokobana/kobana-cli # Install into the profile nix profile install github:universokobana/kobana-cli ``` ### Build from source[​](#build-from-source "Direct link to Build from source") ``` git clone https://github.com/universokobana/kobana-cli.git cd kobana-cli cargo install --path crates/kobana-cli ``` Requires [Rust](https://rustup.rs/) 1.70+. ## Check[​](#check "Direct link to Check") ``` kobana kadu --help ``` The help lists the Kadu resources. If the command says `kadu` is not a product, your CLI version predates Kadu: update it. ## Shell completions[​](#shell-completions "Direct link to Shell completions") ``` # Bash kobana completions bash > /etc/bash_completion.d/kobana # Zsh kobana completions zsh > ~/.zfunc/_kobana # Fish kobana completions fish > ~/.config/fish/completions/kobana.fish # PowerShell kobana completions powershell > kobana.ps1 ``` ## Next steps[​](#next-steps "Direct link to Next steps") * [Authentication](https://docs.kadu.ia.br/en/en/cli/overview/authentication.md) — tokens and environments. * [Usage](https://docs.kadu.ia.br/en/en/cli/usage/overview.md) — syntax, resources, examples. --- # Introduction **`kobana`** is a single command-line tool for the APIs of every Kobana product — the Kadu Agent included. It is built and maintained in its own repository ([`kobana-cli`](https://github.com/universokobana/kobana-cli)), not by this product, and has no hand-written endpoint at all: on startup it reads the OpenAPI 3.1 specs embedded in the binary and builds the command tree from them. Updating the API surface is a matter of updating the embedded spec and publishing a new build — no code per endpoint. ``` kobana [flags] ``` Built for people and AI agents alike: structured JSON output by default, schema introspection, `--dry-run` and automatic pagination. It has no shortcut the API lacks: token, scope, role and limits are the API's. ## Products[​](#products "Direct link to Products") | Product | Description | | ------------ | -------------------------------------------------------------------------------------------- | | `banking` | Banking Gateway — charges, payments, transfers (API v1 and v2) | | `finance` | Smart Finance — accounts payable and receivable, transactions, cash flow | | `billing` | Automated Billing — subscriptions, plans, invoices, tax invoices | | `inbox` | Autonomous Inbox — inboxes, agents, emails, webhooks | | `dunning` | Collection Rules — rules, steps and dispatches | | `dashboards` | Magic Dashboard — dashboards and panels | | `kadu` | Kadu Agent — tasks, executions, pending items, environments, secrets, schedules and webhooks | The `kadu` product talks to the same API documented on this site. Its resources — see the [full catalog](https://docs.kadu.ia.br/en/en/cli/resources/overview.md) — point to the same endpoints as the [API Reference](https://docs.kadu.ia.br/en/en/api/v1); the CLI is just another way to call it. It does not share credentials with the other products: see [Authentication](https://docs.kadu.ia.br/en/en/cli/overview/authentication.md). ## Getting started[​](#getting-started "Direct link to Getting started") 1. **Install** — see [Installation](https://docs.kadu.ia.br/en/en/cli/overview/installation.md). 2. **Authenticate** with the product you will use. See [Authentication](https://docs.kadu.ia.br/en/en/cli/overview/authentication.md). 3. **Make the first call**: ``` kobana kadu tasks list --fields "id,skill_slug,label" ``` 4. **Explore the surface** — every command accepts `--help`, and `kobana schema` shows the parameters and body of any endpoint without leaving the terminal. If the command says `kadu` is not a product, your CLI version predates Kadu: update it (`kobana update` or `brew upgrade kobana`). ## Next steps[​](#next-steps "Direct link to Next steps") * [Installation](https://docs.kadu.ia.br/en/en/cli/overview/installation.md) — Homebrew, prebuilt binaries, Nix, build from source. * [Authentication](https://docs.kadu.ia.br/en/en/cli/overview/authentication.md) — tokens, environments and the `kadu` credentials. * [Usage](https://docs.kadu.ia.br/en/en/cli/usage/overview.md) — syntax, resources, examples. * [Schema introspection](https://docs.kadu.ia.br/en/en/cli/usage/schema.md) — `kobana schema` for parameters and request/response shape. * [Global flags](https://docs.kadu.ia.br/en/en/cli/usage/global-flags.md) — the flags every command accepts. * [Helpers](https://docs.kadu.ia.br/en/en/cli/usage/helpers.md) — `+verb` shortcuts for common flows. * [Environment variables](https://docs.kadu.ia.br/en/en/cli/overview/environment-variables.md) — authentication, configuration and logs. * [Exit codes](https://docs.kadu.ia.br/en/en/cli/overview/exit-codes.md) — process exit codes, for scripts. --- # Activities Groupings of tasks within a workspace. | Command | HTTP | Description | | ------------------- | ---------------------------- | ------------------------------- | | `activities list` | `GET /v1/activities` | List activities | | `activities create` | `POST /v1/activities` | Create activity | | `activities get` | `GET /v1/activities/{id}` | Get activity details | | `activities update` | `PATCH /v1/activities/{id}` | Update activity | | `activities delete` | `DELETE /v1/activities/{id}` | Delete activities (soft delete) | ## Examples[​](#examples "Direct link to Examples") `--params` carries query and path parameters (id, page, filters); `--json` carries the request body; `--fields` limits the fields that come back — worth using on every `list`/`get` to save context. See [Global flags](https://docs.kadu.ia.br/en/en/cli/usage/global-flags.md) for the full reference. ``` # List activities kobana kadu activities list --fields "id" # Create activity kobana kadu activities create \ --json '{"label": "LABEL", "workspace_id": "WORKSPACE_ID"}' # Get activity details kobana kadu activities get --params '{"id": "ACTIVITY_ID"}' # Update activity kobana kadu activities update --params '{"id": "ACTIVITY_ID"}' # Delete activities (soft delete) kobana kadu activities delete \ --params '{"id": "ACTIVITY_ID"}' \ --dry-run ``` ## Next steps[​](#next-steps "Direct link to Next steps") * [Tasks](https://docs.kadu.ia.br/en/en/cli/resources/tasks.md) * [Schema introspection](https://docs.kadu.ia.br/en/en/cli/usage/schema.md) — every parameter this resource accepts. --- # Environments Execution environments (Production, Sandbox…). Each secret and schedule belongs to one. | Command | HTTP | Description | | --------------------- | ------------------------------ | -------------------------------- | | `environments list` | `GET /v1/environments` | List environments | | `environments create` | `POST /v1/environments` | Create environment | | `environments get` | `GET /v1/environments/{id}` | Get environment details | | `environments update` | `PATCH /v1/environments/{id}` | Update environment | | `environments delete` | `DELETE /v1/environments/{id}` | Delete environment (soft delete) | ## Examples[​](#examples "Direct link to Examples") `--params` carries query and path parameters (id, page, filters); `--json` carries the request body; `--fields` limits the fields that come back — worth using on every `list`/`get` to save context. See [Global flags](https://docs.kadu.ia.br/en/en/cli/usage/global-flags.md) for the full reference. ``` # List environments kobana kadu environments list --fields "id" # Create environment kobana kadu environments create \ --json '{"label": "LABEL", "color": "COLOR"}' # Get environment details kobana kadu environments get --params '{"id": "ENVIRONMENT_ID"}' # Update environment kobana kadu environments update --params '{"id": "ENVIRONMENT_ID"}' # Delete environment (soft delete) kobana kadu environments delete \ --params '{"id": "ENVIRONMENT_ID"}' \ --dry-run ``` ## Next steps[​](#next-steps "Direct link to Next steps") * [Secrets](https://docs.kadu.ia.br/en/en/cli/resources/secrets.md) * [Schema introspection](https://docs.kadu.ia.br/en/en/cli/usage/schema.md) — every parameter this resource accepts. --- # Executions Each run of a task, with logs, events, pending items and text search. | Command | HTTP | Description | | ------------------------ | ----------------------------------- | -------------------------------------- | | `executions list` | `GET /v1/executions` | List executions | | `executions get` | `GET /v1/executions/{id}` | Get execution details | | `executions cancel` | `POST /v1/executions/{id}/cancel` | Cancel the execution | | `executions continue` | `POST /v1/executions/{id}/continue` | Continue the conversation (next round) | | `executions search list` | `GET /v1/executions/search` | Text search across executions | | `executions logs` | `GET /v1/executions/{id}/logs` | Execution logs | | `executions events` | `GET /v1/executions/{id}/events` | Execution events | | `executions todos` | `GET /v1/executions/{id}/todos` | Pending items (todos) of the execution | ## Examples[​](#examples "Direct link to Examples") `--params` carries query and path parameters (id, page, filters); `--json` carries the request body; `--fields` limits the fields that come back — worth using on every `list`/`get` to save context. See [Global flags](https://docs.kadu.ia.br/en/en/cli/usage/global-flags.md) for the full reference. ``` # List executions kobana kadu executions list --fields "id" # Get execution details kobana kadu executions get --params '{"id": "EXECUTION_ID"}' # Cancel the execution kobana kadu executions cancel \ --params '{"id": "EXECUTION_ID"}' \ --dry-run # Continue the conversation (next round) kobana kadu executions continue \ --params '{"id": "EXECUTION_ID"}' \ --json '{"message": "MESSAGE"}' \ --dry-run # Text search across executions kobana kadu executions search list --fields "id" # Execution logs kobana kadu executions logs --params '{"id": "EXECUTION_ID"}' # Execution events kobana kadu executions events --params '{"id": "EXECUTION_ID"}' # Pending items (todos) of the execution kobana kadu executions todos --params '{"id": "EXECUTION_ID"}' ``` ## Next steps[​](#next-steps "Direct link to Next steps") * [Memories](https://docs.kadu.ia.br/en/en/cli/resources/memories.md) * [Schema introspection](https://docs.kadu.ia.br/en/en/cli/usage/schema.md) — every parameter this resource accepts. --- # Files Input files (attached to the run) and output files (published by the execution). | Command | HTTP | Description | | ---------------- | ----------------------------- | --------------------------------------- | | `files list` | `GET /v1/files` | List files | | `files create` | `POST /v1/files` | Start an input file upload | | `files get` | `GET /v1/files/{id}` | Get file details | | `files confirm` | `POST /v1/files/{id}/confirm` | Confirm the upload (pending → uploaded) | | `files download` | `GET /v1/files/{id}/download` | Temporary download URL | ## Examples[​](#examples "Direct link to Examples") `--params` carries query and path parameters (id, page, filters); `--json` carries the request body; `--fields` limits the fields that come back — worth using on every `list`/`get` to save context. See [Global flags](https://docs.kadu.ia.br/en/en/cli/usage/global-flags.md) for the full reference. ``` # List files kobana kadu files list --fields "id" # Start an input file upload kobana kadu files create \ --json '{"task_id": "TASK_ID", "filename": "FILENAME", "size_bytes": 0}' # Get file details kobana kadu files get --params '{"id": "FILE_ID"}' # Confirm the upload (pending → uploaded) kobana kadu files confirm --params '{"id": "FILE_ID"}' # Temporary download URL kobana kadu files download --params '{"id": "FILE_ID"}' ``` ## Next steps[​](#next-steps "Direct link to Next steps") * [Repository](https://docs.kadu.ia.br/en/en/cli/resources/repository.md) * [Schema introspection](https://docs.kadu.ia.br/en/en/cli/usage/schema.md) — every parameter this resource accepts. --- # Identity Who is behind the token. | Command | HTTP | Description | | --------- | ------------ | ------------------------- | | `me list` | `GET /v1/me` | Identity behind the token | ## Examples[​](#examples "Direct link to Examples") `--params` carries query and path parameters (id, page, filters); `--json` carries the request body; `--fields` limits the fields that come back — worth using on every `list`/`get` to save context. See [Global flags](https://docs.kadu.ia.br/en/en/cli/usage/global-flags.md) for the full reference. ``` # Identity behind the token kobana kadu me list --fields "id" ``` ## Next steps[​](#next-steps "Direct link to Next steps") * [Organizations](https://docs.kadu.ia.br/en/en/cli/resources/organizations.md) * [Schema introspection](https://docs.kadu.ia.br/en/en/cli/usage/schema.md) — every parameter this resource accepts. --- # Memberships Links between people and the organization. | Command | HTTP | Description | | ------------------ | --------------------- | ---------------- | | `memberships list` | `GET /v1/memberships` | List memberships | ## Examples[​](#examples "Direct link to Examples") `--params` carries query and path parameters (id, page, filters); `--json` carries the request body; `--fields` limits the fields that come back — worth using on every `list`/`get` to save context. See [Global flags](https://docs.kadu.ia.br/en/en/cli/usage/global-flags.md) for the full reference. ``` # List memberships kobana kadu memberships list --fields "id" ``` ## Next steps[​](#next-steps "Direct link to Next steps") * [System events](https://docs.kadu.ia.br/en/en/cli/resources/system-events.md) * [Schema introspection](https://docs.kadu.ia.br/en/en/cli/usage/schema.md) — every parameter this resource accepts. --- # Memories Organization memory: records per entity (`entity_type` + `entity_value`), scoped to the organization, a workspace or an activity. | Command | HTTP | Description | | ----------------- | -------------------------- | --------------------------- | | `memories list` | `GET /v1/memories` | List memories | | `memories create` | `POST /v1/memories` | Create memory | | `memories get` | `GET /v1/memories/{id}` | Get memory details | | `memories update` | `PATCH /v1/memories/{id}` | Update memory | | `memories delete` | `DELETE /v1/memories/{id}` | Delete memory (soft delete) | ## Examples[​](#examples "Direct link to Examples") `--params` carries query and path parameters (id, page, filters); `--json` carries the request body; `--fields` limits the fields that come back — worth using on every `list`/`get` to save context. See [Global flags](https://docs.kadu.ia.br/en/en/cli/usage/global-flags.md) for the full reference. ``` # List memories kobana kadu memories list --fields "id" # Create memory kobana kadu memories create --json '{"content": "CONTENT"}' # Get memory details kobana kadu memories get --params '{"id": "MEMORY_ID"}' # Update memory kobana kadu memories update --params '{"id": "MEMORY_ID"}' # Delete memory (soft delete) kobana kadu memories delete \ --params '{"id": "MEMORY_ID"}' \ --dry-run ``` ## Next steps[​](#next-steps "Direct link to Next steps") * [Environments](https://docs.kadu.ia.br/en/en/cli/resources/environments.md) * [Schema introspection](https://docs.kadu.ia.br/en/en/cli/usage/schema.md) — every parameter this resource accepts. --- # Approvals Operations an assistant requested through MCP that wait for the person's confirmation by link (read-only). | Command | HTTP | Description | | -------------------------- | ---------------------------------- | --------------------------------------- | | `operation-approvals list` | `GET /v1/operation_approvals` | Approvals by link of the token's person | | `operation-approvals get` | `GET /v1/operation_approvals/{id}` | Get an approval by link | ## Examples[​](#examples "Direct link to Examples") `--params` carries query and path parameters (id, page, filters); `--json` carries the request body; `--fields` limits the fields that come back — worth using on every `list`/`get` to save context. See [Global flags](https://docs.kadu.ia.br/en/en/cli/usage/global-flags.md) for the full reference. ``` # Approvals by link of the token's person kobana kadu operation-approvals list --fields "id" # Get an approval by link kobana kadu operation-approvals get \ --params '{"id": "OPERATION_APPROVAL_ID"}' ``` ## Next steps[​](#next-steps "Direct link to Next steps") * [Roles](https://docs.kadu.ia.br/en/en/cli/resources/roles.md) * [Schema introspection](https://docs.kadu.ia.br/en/en/cli/usage/schema.md) — every parameter this resource accepts. --- # Organizations The token's organization: name, time zone, monthly cost cap and `custom_metadata`. | Command | HTTP | Description | | ------------------------------------------ | -------------------------------------------------------------------- | ------------------------------------------- | | `organizations list` | `GET /v1/organizations` | List organizations | | `organizations get` | `GET /v1/organizations/{id}` | Get organization details | | `organizations update` | `PATCH /v1/organizations/{id}` | Update the token's organization | | `organizations slack-installations list` | `GET /v1/organizations/{id}/slack_installations` | Slack bot installations of the organization | | `organizations slack-installations update` | `PATCH /v1/organizations/{id}/slack_installations/{installationId}` | Configures a Slack installation | | `organizations slack-installations delete` | `DELETE /v1/organizations/{id}/slack_installations/{installationId}` | Removes a Slack installation | ## Examples[​](#examples "Direct link to Examples") `--params` carries query and path parameters (id, page, filters); `--json` carries the request body; `--fields` limits the fields that come back — worth using on every `list`/`get` to save context. See [Global flags](https://docs.kadu.ia.br/en/en/cli/usage/global-flags.md) for the full reference. ``` # List organizations kobana kadu organizations list --fields "id" # Get organization details kobana kadu organizations get --params '{"id": "ORGANIZATION_ID"}' # Update the token's organization kobana kadu organizations update --params '{"id": "ORGANIZATION_ID"}' # Slack bot installations of the organization kobana kadu organizations slack-installations list \ --params '{"id": "ORGANIZATION_ID"}' # Configures a Slack installation kobana kadu organizations slack-installations update \ --params '{"id": "ORGANIZATION_ID", "installationId": "INSTALLATION_ID"}' # Removes a Slack installation kobana kadu organizations slack-installations delete \ --params '{"id": "ORGANIZATION_ID", "installationId": "INSTALLATION_ID"}' \ --dry-run ``` ## Next steps[​](#next-steps "Direct link to Next steps") * [Workspaces](https://docs.kadu.ia.br/en/en/cli/resources/workspaces.md) * [Schema introspection](https://docs.kadu.ia.br/en/en/cli/usage/schema.md) — every parameter this resource accepts. --- # Resources Every `kadu` resource below is generated from the same OpenAPI spec documented in the [API Reference](https://docs.kadu.ia.br/en/en/api/v1) — the CLI calls exactly the same endpoints, so filters, validation, scopes and deletion behavior are identical. Use [schema introspection](https://docs.kadu.ia.br/en/en/cli/usage/schema.md) whenever the exact parameters of a command are not obvious from the examples on these pages. ``` kobana kadu [flags] ``` ## How to read these pages[​](#how-to-read-these-pages "Direct link to How to read these pages") `--params` carries query and path parameters (id, page, filters); `--json` carries the request body; `--fields` limits the fields that come back — worth using on every `list`/`get`, to save context. See [Global flags](https://docs.kadu.ia.br/en/en/cli/usage/global-flags.md) for the full reference. See [Authentication](https://docs.kadu.ia.br/en/en/cli/overview/authentication.md) for the `kadu` credentials. ## The catalog[​](#the-catalog "Direct link to The catalog") | Resource | Command | Methods | | --------------------------------------------------------------------------------------- | --------------------- | --------------------------------------------------------------------------------------------------- | | [Identity](https://docs.kadu.ia.br/en/en/cli/resources/me.md) | `me` | list | | [Organizations](https://docs.kadu.ia.br/en/en/cli/resources/organizations.md) | `organizations` | list, get, update, slack-installations list, slack-installations update, slack-installations delete | | [Workspaces](https://docs.kadu.ia.br/en/en/cli/resources/workspaces.md) | `workspaces` | list, create, get, update, delete | | [Activities](https://docs.kadu.ia.br/en/en/cli/resources/activities.md) | `activities` | list, create, get, update, delete | | [Tasks](https://docs.kadu.ia.br/en/en/cli/resources/tasks.md) | `tasks` | list, create, get, update, delete, run, endpoint get, endpoint update, endpoint delete, readiness | | [Executions](https://docs.kadu.ia.br/en/en/cli/resources/executions.md) | `executions` | list, get, cancel, continue, search list, logs, events, todos | | [Memories](https://docs.kadu.ia.br/en/en/cli/resources/memories.md) | `memories` | list, create, get, update, delete | | [Environments](https://docs.kadu.ia.br/en/en/cli/resources/environments.md) | `environments` | list, create, get, update, delete | | [Secrets](https://docs.kadu.ia.br/en/en/cli/resources/secrets.md) | `secrets` | list, create, get, update, delete | | [Schedules](https://docs.kadu.ia.br/en/en/cli/resources/schedules.md) | `schedules` | list, create, get, update, delete | | [Files](https://docs.kadu.ia.br/en/en/cli/resources/files.md) | `files` | list, create, get, confirm, download | | [Repository](https://docs.kadu.ia.br/en/en/cli/resources/repository.md) | `repository` | list, sync create | | [Usage](https://docs.kadu.ia.br/en/en/cli/resources/usage.md) | `usage` | list | | [Pending items](https://docs.kadu.ia.br/en/en/cli/resources/todos.md) | `todos` | update | | [Webhooks](https://docs.kadu.ia.br/en/en/cli/resources/webhooks.md) | `webhooks` | list, create, get, update, delete, rotate-secret | | [Webhook deliveries](https://docs.kadu.ia.br/en/en/cli/resources/webhook-deliveries.md) | `webhook-deliveries` | list, get, replay | | [Approvals](https://docs.kadu.ia.br/en/en/cli/resources/operation-approvals.md) | `operation-approvals` | list, get | | [Roles](https://docs.kadu.ia.br/en/en/cli/resources/roles.md) | `roles` | list, create | | [Users](https://docs.kadu.ia.br/en/en/cli/resources/users.md) | `users` | list | | [Memberships](https://docs.kadu.ia.br/en/en/cli/resources/memberships.md) | `memberships` | list | | [System events](https://docs.kadu.ia.br/en/en/cli/resources/system-events.md) | `system-events` | list | --- # Repository The organization's Kadu Playbook and its sync. | Command | HTTP | Description | | ------------------------ | -------------------------- | -------------------------- | | `repository list` | `GET /v1/repository` | Organization Kadu Playbook | | `repository sync create` | `POST /v1/repository/sync` | Syncs the Kadu Playbook | ## Examples[​](#examples "Direct link to Examples") `--params` carries query and path parameters (id, page, filters); `--json` carries the request body; `--fields` limits the fields that come back — worth using on every `list`/`get` to save context. See [Global flags](https://docs.kadu.ia.br/en/en/cli/usage/global-flags.md) for the full reference. ``` # Organization Kadu Playbook kobana kadu repository list --fields "id" # Syncs the Kadu Playbook kobana kadu repository sync create ``` ## Next steps[​](#next-steps "Direct link to Next steps") * [Usage](https://docs.kadu.ia.br/en/en/cli/resources/usage.md) * [Schema introspection](https://docs.kadu.ia.br/en/en/cli/usage/schema.md) — every parameter this resource accepts. --- # Roles Roles and the `agent.*` permissions they grant. | Command | HTTP | Description | | -------------- | ---------------- | ----------- | | `roles list` | `GET /v1/roles` | List roles | | `roles create` | `POST /v1/roles` | Create role | ## Examples[​](#examples "Direct link to Examples") `--params` carries query and path parameters (id, page, filters); `--json` carries the request body; `--fields` limits the fields that come back — worth using on every `list`/`get` to save context. See [Global flags](https://docs.kadu.ia.br/en/en/cli/usage/global-flags.md) for the full reference. ``` # List roles kobana kadu roles list --fields "id" # Create role kobana kadu roles create --json '{"label": "LABEL"}' ``` ## Next steps[​](#next-steps "Direct link to Next steps") * [Users](https://docs.kadu.ia.br/en/en/cli/resources/users.md) * [Schema introspection](https://docs.kadu.ia.br/en/en/cli/usage/schema.md) — every parameter this resource accepts. --- # Schedules Recurring executions of a task in an environment, defined by a cron expression. | Command | HTTP | Description | | ------------------ | --------------------------- | ---------------------------------- | | `schedules list` | `GET /v1/schedules` | List schedules | | `schedules create` | `POST /v1/schedules` | Schedule a Task in an environment | | `schedules get` | `GET /v1/schedules/{id}` | Get schedule details | | `schedules update` | `PATCH /v1/schedules/{id}` | Update, pause or resume a schedule | | `schedules delete` | `DELETE /v1/schedules/{id}` | Cancel schedule (soft delete) | ## Examples[​](#examples "Direct link to Examples") `--params` carries query and path parameters (id, page, filters); `--json` carries the request body; `--fields` limits the fields that come back — worth using on every `list`/`get` to save context. See [Global flags](https://docs.kadu.ia.br/en/en/cli/usage/global-flags.md) for the full reference. ``` # List schedules kobana kadu schedules list --fields "id" # Schedule a Task in an environment kobana kadu schedules create \ --json '{"task_id": "TASK_ID", "environment_id": "ENVIRONMENT_ID", "cron": "CRON"}' # Get schedule details kobana kadu schedules get --params '{"id": "SCHEDULE_ID"}' # Update, pause or resume a schedule kobana kadu schedules update --params '{"id": "SCHEDULE_ID"}' # Cancel schedule (soft delete) kobana kadu schedules delete \ --params '{"id": "SCHEDULE_ID"}' \ --dry-run ``` ## Next steps[​](#next-steps "Direct link to Next steps") * [Files](https://docs.kadu.ia.br/en/en/cli/resources/files.md) * [Schema introspection](https://docs.kadu.ia.br/en/en/cli/usage/schema.md) — every parameter this resource accepts. --- # Secrets Credentials delivered to executions. The value is stored encrypted and is never returned. | Command | HTTP | Description | | ---------------- | ------------------------- | --------------------------- | | `secrets list` | `GET /v1/secrets` | List secrets | | `secrets create` | `POST /v1/secrets` | Create secret | | `secrets get` | `GET /v1/secrets/{id}` | Get secret details | | `secrets update` | `PATCH /v1/secrets/{id}` | Update secret | | `secrets delete` | `DELETE /v1/secrets/{id}` | Delete secret (soft delete) | ## Examples[​](#examples "Direct link to Examples") `--params` carries query and path parameters (id, page, filters); `--json` carries the request body; `--fields` limits the fields that come back — worth using on every `list`/`get` to save context. See [Global flags](https://docs.kadu.ia.br/en/en/cli/usage/global-flags.md) for the full reference. ``` # List secrets kobana kadu secrets list --fields "id" # Create secret kobana kadu secrets create \ --json '{"environment_id": "ENVIRONMENT_ID", "key": "KEY", "value": "VALUE"}' # Get secret details kobana kadu secrets get --params '{"id": "SECRET_ID"}' # Update secret kobana kadu secrets update --params '{"id": "SECRET_ID"}' # Delete secret (soft delete) kobana kadu secrets delete \ --params '{"id": "SECRET_ID"}' \ --dry-run ``` ## Next steps[​](#next-steps "Direct link to Next steps") * [Schedules](https://docs.kadu.ia.br/en/en/cli/resources/schedules.md) * [Schema introspection](https://docs.kadu.ia.br/en/en/cli/usage/schema.md) — every parameter this resource accepts. --- # System events Organization events (the same ones that feed webhooks). | Command | HTTP | Description | | -------------------- | ----------------------- | ------------------------------- | | `system-events list` | `GET /v1/system_events` | System events (100 most recent) | ## Examples[​](#examples "Direct link to Examples") `--params` carries query and path parameters (id, page, filters); `--json` carries the request body; `--fields` limits the fields that come back — worth using on every `list`/`get` to save context. See [Global flags](https://docs.kadu.ia.br/en/en/cli/usage/global-flags.md) for the full reference. ``` # System events (100 most recent) kobana kadu system-events list --fields "id" ``` ## Next steps[​](#next-steps "Direct link to Next steps") * [Schema introspection](https://docs.kadu.ia.br/en/en/cli/usage/schema.md) — every parameter this resource accepts. --- # Tasks The organization's executable skills: registration, run, pre-check and the HTTP run endpoint. | Command | HTTP | Description | | ----------------------- | -------------------------------- | ---------------------------------- | | `tasks list` | `GET /v1/tasks` | List tasks | | `tasks create` | `POST /v1/tasks` | Create task | | `tasks get` | `GET /v1/tasks/{id}` | Get task details | | `tasks update` | `PATCH /v1/tasks/{id}` | Update task | | `tasks delete` | `DELETE /v1/tasks/{id}` | Delete tasks (soft delete) | | `tasks run` | `POST /v1/tasks/{id}/run` | Run task | | `tasks endpoint get` | `GET /v1/tasks/{id}/endpoint` | Task HTTP run endpoint | | `tasks endpoint update` | `PUT /v1/tasks/{id}/endpoint` | Configure the Task's HTTP endpoint | | `tasks endpoint delete` | `DELETE /v1/tasks/{id}/endpoint` | Disable the Task's HTTP endpoint | | `tasks readiness` | `GET /v1/tasks/{id}/readiness` | Run pre-check | ## Examples[​](#examples "Direct link to Examples") `--params` carries query and path parameters (id, page, filters); `--json` carries the request body; `--fields` limits the fields that come back — worth using on every `list`/`get` to save context. See [Global flags](https://docs.kadu.ia.br/en/en/cli/usage/global-flags.md) for the full reference. ``` # List tasks kobana kadu tasks list --fields "id" # Create task kobana kadu tasks create \ --json '{"label": "LABEL", "workspace_id": "WORKSPACE_ID", "activity_id": "ACTIVITY_ID", "skill_slug": "finance:task-baixar-pagamentos-via-boleto-pix"}' # Get task details kobana kadu tasks get --params '{"id": "TASK_ID"}' # Update task kobana kadu tasks update --params '{"id": "TASK_ID"}' # Delete tasks (soft delete) kobana kadu tasks delete \ --params '{"id": "TASK_ID"}' \ --dry-run # Run task kobana kadu tasks run \ --params '{"id": "TASK_ID"}' \ --json '{"reference_date": "2026-10-30", "environment_id": "ENVIRONMENT_ID"}' \ --dry-run # Task HTTP run endpoint kobana kadu tasks endpoint get --params '{"id": "TASK_ID"}' # Configure the Task's HTTP endpoint kobana kadu tasks endpoint update \ --params '{"id": "TASK_ID"}' \ --json '{"enabled": true, "auth": "basic"}' # Disable the Task's HTTP endpoint kobana kadu tasks endpoint delete \ --params '{"id": "TASK_ID"}' \ --dry-run # Run pre-check kobana kadu tasks readiness --params '{"id": "TASK_ID"}' ``` ## Next steps[​](#next-steps "Direct link to Next steps") * [Executions](https://docs.kadu.ia.br/en/en/cli/resources/executions.md) * [Schema introspection](https://docs.kadu.ia.br/en/en/cli/usage/schema.md) — every parameter this resource accepts. --- # Pending items Pending items created by executions. | Command | HTTP | Description | | -------------- | ---------------------- | -------------------- | | `todos update` | `PATCH /v1/todos/{id}` | Resolve pending item | ## Examples[​](#examples "Direct link to Examples") `--params` carries query and path parameters (id, page, filters); `--json` carries the request body; `--fields` limits the fields that come back — worth using on every `list`/`get` to save context. See [Global flags](https://docs.kadu.ia.br/en/en/cli/usage/global-flags.md) for the full reference. ``` # Resolve pending item kobana kadu todos update --params '{"id": "TODO_ID"}' ``` ## Next steps[​](#next-steps "Direct link to Next steps") * [Webhooks](https://docs.kadu.ia.br/en/en/cli/resources/webhooks.md) * [Schema introspection](https://docs.kadu.ia.br/en/en/cli/usage/schema.md) — every parameter this resource accepts. --- # Usage Tokens, cost and price of executions. | Command | HTTP | Description | | ------------ | --------------- | ---------------------------------------------- | | `usage list` | `GET /v1/usage` | Usage and cost of executions and conversations | ## Examples[​](#examples "Direct link to Examples") `--params` carries query and path parameters (id, page, filters); `--json` carries the request body; `--fields` limits the fields that come back — worth using on every `list`/`get` to save context. See [Global flags](https://docs.kadu.ia.br/en/en/cli/usage/global-flags.md) for the full reference. ``` # Usage and cost of executions and conversations kobana kadu usage list --fields "id" ``` ## Next steps[​](#next-steps "Direct link to Next steps") * [Pending items](https://docs.kadu.ia.br/en/en/cli/resources/todos.md) * [Schema introspection](https://docs.kadu.ia.br/en/en/cli/usage/schema.md) — every parameter this resource accepts. --- # Users Organization members. | Command | HTTP | Description | | ------------ | --------------- | -------------------------------- | | `users list` | `GET /v1/users` | Organization members (up to 100) | ## Examples[​](#examples "Direct link to Examples") `--params` carries query and path parameters (id, page, filters); `--json` carries the request body; `--fields` limits the fields that come back — worth using on every `list`/`get` to save context. See [Global flags](https://docs.kadu.ia.br/en/en/cli/usage/global-flags.md) for the full reference. ``` # Organization members (up to 100) kobana kadu users list --fields "id" ``` ## Next steps[​](#next-steps "Direct link to Next steps") * [Memberships](https://docs.kadu.ia.br/en/en/cli/resources/memberships.md) * [Schema introspection](https://docs.kadu.ia.br/en/en/cli/usage/schema.md) — every parameter this resource accepts. --- # Webhook deliveries History and resending of webhook deliveries. | Command | HTTP | Description | | --------------------------- | ----------------------------------------- | ----------------------------------- | | `webhook-deliveries list` | `GET /v1/webhook_deliveries` | Webhook deliveries (50 most recent) | | `webhook-deliveries get` | `GET /v1/webhook_deliveries/{id}` | Get webhook delivery details | | `webhook-deliveries replay` | `POST /v1/webhook_deliveries/{id}/replay` | Requeue the delivery | ## Examples[​](#examples "Direct link to Examples") `--params` carries query and path parameters (id, page, filters); `--json` carries the request body; `--fields` limits the fields that come back — worth using on every `list`/`get` to save context. See [Global flags](https://docs.kadu.ia.br/en/en/cli/usage/global-flags.md) for the full reference. ``` # Webhook deliveries (50 most recent) kobana kadu webhook-deliveries list --fields "id" # Get webhook delivery details kobana kadu webhook-deliveries get \ --params '{"id": "WEBHOOK_DELIVERY_ID"}' # Requeue the delivery kobana kadu webhook-deliveries replay \ --params '{"id": "WEBHOOK_DELIVERY_ID"}' \ --dry-run ``` ## Next steps[​](#next-steps "Direct link to Next steps") * [Approvals](https://docs.kadu.ia.br/en/en/cli/resources/operation-approvals.md) * [Schema introspection](https://docs.kadu.ia.br/en/en/cli/usage/schema.md) — every parameter this resource accepts. --- # Webhooks Signed HTTP notifications sent when events occur. | Command | HTTP | Description | | ------------------------ | -------------------------------------- | ----------------------------- | | `webhooks list` | `GET /v1/webhooks` | List webhooks | | `webhooks create` | `POST /v1/webhooks` | Create webhook | | `webhooks get` | `GET /v1/webhooks/{id}` | Get webhook details | | `webhooks update` | `PATCH /v1/webhooks/{id}` | Update webhook | | `webhooks delete` | `DELETE /v1/webhooks/{id}` | Delete webhooks (soft delete) | | `webhooks rotate-secret` | `POST /v1/webhooks/{id}/rotate-secret` | Rotate the signing secret | ## Examples[​](#examples "Direct link to Examples") `--params` carries query and path parameters (id, page, filters); `--json` carries the request body; `--fields` limits the fields that come back — worth using on every `list`/`get` to save context. See [Global flags](https://docs.kadu.ia.br/en/en/cli/usage/global-flags.md) for the full reference. ``` # List webhooks kobana kadu webhooks list --fields "id" # Create webhook kobana kadu webhooks create \ --json '{"label": "LABEL", "url": "https://example.com", "auth": "basic"}' # Get webhook details kobana kadu webhooks get --params '{"id": "WEBHOOK_ID"}' # Update webhook kobana kadu webhooks update --params '{"id": "WEBHOOK_ID"}' # Delete webhooks (soft delete) kobana kadu webhooks delete \ --params '{"id": "WEBHOOK_ID"}' \ --dry-run # Rotate the signing secret kobana kadu webhooks rotate-secret \ --params '{"id": "WEBHOOK_ID"}' \ --dry-run ``` ## Next steps[​](#next-steps "Direct link to Next steps") * [Webhook deliveries](https://docs.kadu.ia.br/en/en/cli/resources/webhook-deliveries.md) * [Schema introspection](https://docs.kadu.ia.br/en/en/cli/usage/schema.md) — every parameter this resource accepts. --- # Workspaces Business domains of the organization. Each workspace is a plugin of the Kadu Playbook. | Command | HTTP | Description | | ------------------- | ---------------------------- | ------------------------------- | | `workspaces list` | `GET /v1/workspaces` | List workspaces | | `workspaces create` | `POST /v1/workspaces` | Create workspace | | `workspaces get` | `GET /v1/workspaces/{id}` | Get workspace details | | `workspaces update` | `PATCH /v1/workspaces/{id}` | Update workspace | | `workspaces delete` | `DELETE /v1/workspaces/{id}` | Delete workspaces (soft delete) | ## Examples[​](#examples "Direct link to Examples") `--params` carries query and path parameters (id, page, filters); `--json` carries the request body; `--fields` limits the fields that come back — worth using on every `list`/`get` to save context. See [Global flags](https://docs.kadu.ia.br/en/en/cli/usage/global-flags.md) for the full reference. ``` # List workspaces kobana kadu workspaces list --fields "id" # Create workspace kobana kadu workspaces create --json '{"label": "LABEL"}' # Get workspace details kobana kadu workspaces get --params '{"id": "WORKSPACE_ID"}' # Update workspace kobana kadu workspaces update --params '{"id": "WORKSPACE_ID"}' # Delete workspaces (soft delete) kobana kadu workspaces delete \ --params '{"id": "WORKSPACE_ID"}' \ --dry-run ``` ## Next steps[​](#next-steps "Direct link to Next steps") * [Activities](https://docs.kadu.ia.br/en/en/cli/resources/activities.md) * [Schema introspection](https://docs.kadu.ia.br/en/en/cli/usage/schema.md) — every parameter this resource accepts. --- # Global flags | Flag | Description | | ------------------------- | ---------------------------------------------------- | | `--params ''` | Query and URL parameters (id, page, filters) | | `--json ''` | Request body (POST/PUT/PATCH) | | `--fields ''` | Limits the fields returned in the response | | `--dry-run` | Shows the request without running it | | `--page-all` | Automatic pagination, with NDJSON output | | `--page-limit ` | Maximum pages (default: 10) | | `--page-delay ` | Interval between pages (default: 100ms) | | `--env ` | Environment: `production` (default) or `development` | | `--verbose` | Request details on stderr | | `--output ` | Saves the response to a file | | `--output-format ` | Format: `json`, `table`, `csv` | | `--idempotency-key ` | Custom idempotency key | **`--dry-run`** shows the method, URL, query and body of the request without sending it. Use it before every action that changes data, triggers the agent or deletes. It does not validate the body against the spec: a missing required field only shows up in the API response (`400 VALIDATION_ERROR`). **Pagination.** Listings are cursor-based. With `--page-all`, the CLI follows `meta.next_cursor` to the last page and prints one item per line (NDJSON). `limit` in `--params` sets each page size (up to `100`). The rules are those of the API's [Pagination](https://docs.kadu.ia.br/en/en/api/overview/pagination.md). **Idempotency.** Every `POST`, `PUT` and `PATCH` carries an `X-Idempotency-Key`; the CLI generates a new key per command. To safely repeat a call that failed midway (triggering a task, for example), pass the same key both times with `--idempotency-key`. The rules are in [Idempotency](https://docs.kadu.ia.br/en/en/api/overview/idempotency.md). ## Next steps[​](#next-steps "Direct link to Next steps") * [Usage](https://docs.kadu.ia.br/en/en/cli/usage/overview.md) — syntax, resources, examples. * [Schema introspection](https://docs.kadu.ia.br/en/en/cli/usage/schema.md) — parameters, required fields, types and responses. --- # Helpers Helpers are `+`-prefixed commands that deliver multi-step flows the dynamic commands alone do not: simplified interfaces, batch operations or calls composed of several endpoints. They do not wrap a single API call already available through the dynamic commands. The Kadu Agent **has no helper of its own**: everything it offers is in the dynamic `kobana kadu` commands. Today's helpers belong to the **Banking Gateway** and ship in the same binary: | Helper | Description | | ---------------- | ------------------------------------------------ | | `+emitir` | Simplified bank slip issuing, with named flags | | `+cancelar-lote` | Batch cancellation of several bank slips by id | | `+cobrar` | Simplified Pix charge creation, with named flags | ## Next steps[​](#next-steps "Direct link to Next steps") * [Usage](https://docs.kadu.ia.br/en/en/cli/usage/overview.md) — the dynamic commands these helpers live next to. * [Schema introspection](https://docs.kadu.ia.br/en/en/cli/usage/schema.md) — inspect the endpoints a helper calls underneath. --- # Usage ## Syntax[​](#syntax "Direct link to Syntax") ``` kobana [flags] ``` * **``** — `banking`, `finance`, `billing`, `inbox`, `dunning`, `dashboards` or `kadu`. * **``** — can be nested: `tasks`, `executions search`, `webhook-deliveries`. It is the first segment after `/v1`, with `-` in place of `_`. * **``** — the action: `list`, `get`, `create`, `update`, `delete`, or the endpoint's action name (`run`, `cancel`, `replay`). The API version never appears in the command — the CLI resolves the URL from the embedded OpenAPI spec. URL and query parameters go in `--params`; the request body goes in `--json`. The CLI knows from the spec where each parameter goes: `id` goes in the path, the rest in the query. The whole surface is discovered with `--help`, at any level: ``` kobana --help # products kobana kadu --help # Kadu resources kobana kadu tasks --help # methods of a resource ``` ## `kadu` resources[​](#kadu-resources "Direct link to kadu-resources") | Resource | Description | | --------------------- | --------------------------------------------------------------------------------------------------------------------------------- | | `me` | Who is behind the token. | | `organizations` | The token's organization: name, time zone, monthly cost cap and `custom_metadata`. | | `workspaces` | Business domains of the organization. Each workspace is a plugin of the Kadu Playbook. | | `activities` | Groupings of tasks within a workspace. | | `tasks` | The organization's executable skills: registration, run, pre-check and the HTTP run endpoint. | | `executions` | Each run of a task, with logs, events, pending items and text search. | | `memories` | Organization memory: records per entity (`entity_type` + `entity_value`), scoped to the organization, a workspace or an activity. | | `environments` | Execution environments (Production, Sandbox…). Each secret and schedule belongs to one. | | `secrets` | Credentials delivered to executions. The value is stored encrypted and is never returned. | | `schedules` | Recurring executions of a task in an environment, defined by a cron expression. | | `files` | Input files (attached to the run) and output files (published by the execution). | | `repository` | The organization's Kadu Playbook and its sync. | | `usage` | Tokens, cost and price of executions. | | `todos` | Pending items created by executions. | | `webhooks` | Signed HTTP notifications sent when events occur. | | `webhook-deliveries` | History and resending of webhook deliveries. | | `operation-approvals` | Operations an assistant requested through MCP that wait for the person's confirmation by link (read-only). | | `roles` | Roles and the `agent.*` permissions they grant. | | `users` | Organization members. | | `memberships` | Links between people and the organization. | | `system-events` | Organization events (the same ones that feed webhooks). | Each resource has its own page, with the full command table and examples — see the [catalog](https://docs.kadu.ia.br/en/en/cli/resources/overview.md). ## Examples[​](#examples "Direct link to Examples") ``` # Executions that failed or were cancelled this month kobana kadu executions list \ --params '{"status[in]": "failed,cancelled", "reference_date[gte]": "2026-10-01"}' \ --fields "id,status,reference_date" # Every page, one item per line (NDJSON) kobana kadu executions list --page-all --fields "id,status" # Table output kobana kadu tasks list --output-format table # Save the response to a file kobana kadu tasks get --params '{"id": "TASK_ID"}' --output tarefa.json ``` ### Example: trigger and follow[​](#example-trigger-and-follow "Direct link to Example: trigger and follow") ``` # 1. The task and the environment kobana kadu tasks list --fields "id,skill_slug,label" kobana kadu environments list --fields "id,label" # 2. Check the environment is ready kobana kadu tasks readiness --params '{"id": "TASK_ID"}' # 3. Trigger (first with --dry-run) kobana kadu tasks run --dry-run --params '{"id": "TASK_ID"}' \ --json '{"reference_date": "2026-10-30", "environment_id": "ENVIRONMENT_ID"}' # 4. Follow the execution kobana kadu executions get --params '{"id": "EXECUTION_ID"}' --fields "id,status" kobana kadu executions events --params '{"id": "EXECUTION_ID"}' kobana kadu executions logs --params '{"id": "EXECUTION_ID"}' # 5. Resolve the pending items it left kobana kadu executions todos --params '{"id": "EXECUTION_ID"}' kobana kadu todos update --params '{"id": "TODO_ID"}' --json '{"status": "resolved"}' ``` ## Next steps[​](#next-steps "Direct link to Next steps") * [Schema introspection](https://docs.kadu.ia.br/en/en/cli/usage/schema.md) — inspect the parameters and body of any endpoint. * [Global flags](https://docs.kadu.ia.br/en/en/cli/usage/global-flags.md) — every flag a command accepts. * [Helpers](https://docs.kadu.ia.br/en/en/cli/usage/helpers.md) — `+verb` shortcuts for common flows. * [Resources](https://docs.kadu.ia.br/en/en/cli/resources/overview.md) — the full catalog, resource by resource. --- # Schema introspection ``` # List every product and its resources kobana schema --list # See the schema of a specific endpoint kobana schema kadu.tasks.run kobana schema kadu.tasks.endpoint.update kobana schema kadu.executions.search.list ``` Returns parameters, required fields, types and responses — all derived from the embedded OpenAPI spec, so it is always in sync with what the command actually accepts. The path follows the same shape as the command: `..`. This is the recommended first step for an AI agent that does not yet know a payload's exact format: inspect the schema before firing a call that changes data, instead of guessing. ## Next steps[​](#next-steps "Direct link to Next steps") * [Global flags](https://docs.kadu.ia.br/en/en/cli/usage/global-flags.md) — `--params`, `--json`, `--dry-run` and the rest. * [Usage](https://docs.kadu.ia.br/en/en/cli/usage/overview.md) — syntax, resources, examples. --- # Environments and secrets ## Environments[​](#environments "Direct link to Environments") An **environment** separates the settings of the same task: for example, **Production**, with the systems' real tokens, and **Sandbox**, with the test ones. Every execution runs in an environment, chosen in the console's top bar or given in the run call through the API (`environment_id`). Each environment has: | Field | What it is | | --------- | --------------------------------------------------------- | | Name | The environment's label, unique within the organization. | | Color | The color that identifies the environment in the console. | | Branch | The branch used by executions in this environment. | | Secrets | The credentials registered for it. | | Schedules | The schedules of the tasks that run in it. | An environment with secrets or schedules cannot be deleted: remove them first. Environments can be declared in the Playbook, in the `kadu.yaml` file ([The `kadu.yaml`](https://docs.kadu.ia.br/en/en/playbook/kadu-yaml.md)). In that case, the Playbook is the source of truth: an edit made only in the console reverts to what was declared on the next push. ## Secrets[​](#secrets "Direct link to Secrets") A **secret** is a credential that a task uses: an API token, a password, an account id. It belongs to an environment and, optionally, to a workspace. * The value is stored **encrypted** and is never shown again, neither in the console nor in the API. To change it, save a new value. * An execution receives **only the secrets the task declares** in `required_secrets`. It is an allowlist: a registered secret that is not declared does not reach the execution. * The secret reaches the skill as an environment variable named after the key (`KOBANA_ACCESS_TOKEN`, `FINTERA_TOKEN`…). * Before sending an event, a log or a pending item to the console, Kadu replaces the value of every secret of the execution with a mask. ### Pending secrets[​](#pending-secrets "Direct link to Pending secrets") **Settings → Secrets** marks as **Pending** every secret that a task in the workspace requires and that has no value in the environment yet. A run from the console checks this before starting and shows what is missing, with the **Configure secrets** shortcut. On the [run endpoint](https://docs.kadu.ia.br/en/en/api/overview/run-task-endpoint.md), a required secret with no value rejects the call with `422 missing_secrets`. ### Order when adding a new secret to a task[​](#order-when-adding-a-new-secret-to-a-task "Direct link to Order when adding a new secret to a task") 1. Whoever maintains the Playbook adds the name to the skill's `required_secrets` and publishes. 2. After the sync, register the value in the environment. If the task has a schedule, register the value before the next run time. Without it, the execution receives the variable empty and fails midway, not at the start. --- # Organization, workspaces and activities Everything in Kadu belongs to an **organization**, and its tasks are arranged in three levels: ``` Organization ← your company; has a Playbook └── Workspace ← a business domain (Finance, Collections…) └── Activity ← a grouping (Receivables Settlement, Reconciliation…) └── Task ← what Kadu runs └── Execution ← each time the task runs ``` | Level | What it is | Where it comes from | | ---------------- | ---------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | | **Organization** | Your company in Kadu. It holds the contracted plan, the monthly cost cap, the time zone, the members and the Playbook. | Created by Kobana, or at sign-up through the GitHub App. | | **Workspace** | A business domain. In the Playbook, each workspace is **a plugin**. | Created by the repository sync, one per plugin. | | **Activity** | Groups similar tasks within a workspace. | Declared in each skill's `task:` block (`task.activity`); the sync creates any that is missing. | | **Task** | A skill that can be run from the console, by a schedule, through the API or by an HTTP call. | A repository skill with a `task:` block. | | **Execution** | One run of the task, with timeline, logs, pending items, files, result and cost. | Created on every run. | ## The repository rules the structure[​](#the-repository-rules-the-structure "Direct link to The repository rules the structure") Workspaces, activities and tasks come from the organization's Playbook and are updated on every push. The console shows this structure in **Settings → Content**, along with the skills that have not become tasks yet. To change the name of a task, its activity or the secrets it asks for, change the skill file in the repository. An edit made only in the console is undone on the next sync. See [Kadu Playbook](https://docs.kadu.ia.br/en/en/playbook.md) and [Sync](https://docs.kadu.ia.br/en/en/playbook/sincronizacao.md). ## Members and roles[​](#members-and-roles "Direct link to Members and roles") Whoever signs in to the console is a member of the organization, with a **role** that defines what they can do. Permissions have the format `agent..`, for example `agent.executions.run` to run a task or `agent.secrets.update` to change a secret. The same roles apply to anyone using the [API](https://docs.kadu.ia.br/en/en/api/overview/permissions.md) with a personal token. Some screens also depend on what Kobana has enabled for the organization: without the permission, the screen is read-only or does not appear. --- # Executions An **execution** is each time a task runs. It stores who triggered it, in which environment, with which parameters, everything the agent did and how much it cost. ## Where a run comes from[​](#where-a-run-comes-from "Direct link to Where a run comes from") | Source | How | | -------- | ------------------------------------------------------------------------------------------------------------------------------------- | | Console | **Run now** button ([Run a task](https://docs.kadu.ia.br/en/en/console/executar-tarefa.md)) | | Schedule | the task's cron expression in an environment ([Schedules](https://docs.kadu.ia.br/en/en/configuracoes/agendamentos.md)) | | API | `POST /v1/tasks/{id}/run` ([API v1](https://docs.kadu.ia.br/en/en/api/overview/introduction.md)) | | Endpoint | `POST /run-task/{task_id}` called by another system ([Run endpoint](https://docs.kadu.ia.br/en/en/api/overview/run-task-endpoint.md)) | | Manage | run by the Kobana team | The source appears in the **Source** column of **Analytics → Logs**. ## Status[​](#status "Direct link to Status") | Status | In the console | Meaning | | ----------- | -------------- | --------------------------------------------------------------------------------------- | | `queued` | Queued | Accepted, waiting for the previous execution of the same task to finish. | | `pending` | Pending | Sent to run. The agent starts within 3 minutes. | | `running` | Running | The agent is working. | | `success` | Done | Finished and saved the result. | | `failed` | Failed | Finished with an error (the skill exited with a failure, timed out or could not start). | | `cancelled` | Cancelled | Interrupted by a person. What has already been done in external systems is not undone. | ## One execution per task at a time[​](#one-execution-per-task-at-a-time "Direct link to One execution per task at a time") Each task runs **one execution at a time**. A run while another execution of the same task is in progress is not rejected: it joins the queue (`queued`) and starts on its own when the previous ones finish. The console tells you how many executions are ahead before you confirm. Schedules are the exception: if the task already has an active or queued execution at the scheduled time, that run is **skipped**, not stacked. A cron shorter than the task's duration loses runs. Different tasks run in parallel. ## What an execution records[​](#what-an-execution-records "Direct link to What an execution records") | Tab | Content | | ------------ | ------------------------------------------------------------------------------------------ | | **Timeline** | The timeline of events the skill emitted: start and end of each step, warnings and errors. | | **Logs** | The log lines and the HTTP calls to external systems, with secrets masked. | | **Pending** | What the skill left for a person to resolve. | | **Files** | The input files attached to the run and the output files published by the execution. | | **Result** | The final report in Markdown. | | **Cost** | Tokens consumed (input, output, cache), tier and cost. | See [Follow an execution](https://docs.kadu.ia.br/en/en/console/acompanhar-execucao.md). ## Limits[​](#limits "Direct link to Limits") * **Time:** an execution runs for up to 60 minutes. After that, it is interrupted and marked as failed. * **Plan and cost cap:** without Kadu in the organization's plan, or with the monthly cost cap reached, the run is rejected. See [Plan and costs](https://docs.kadu.ia.br/en/en/conceitos/plano-e-custos.md). * **Retention:** events, logs and webhook deliveries are kept for 90 days. --- # Plan and costs ## Contracted plan[​](#contracted-plan "Direct link to Contracted plan") Running a task requires **Kadu to be in the organization's plan** with Kobana (active, in trial or past due). The contract is read from Kobana's billing once a day, at sign-in and when the Kobana team refreshes the organization. Without a plan, only the run is blocked, on every path: console, API, run endpoint and schedules. Registration, reading, settings and cancellation remain available. The console shows the **Subscribe** bar, and the API responds `403 ENTITLEMENT_REQUIRED`. A schedule that stops running for lack of a plan emits the `organization.entitlement_required` event to your [webhooks](https://docs.kadu.ia.br/en/en/configuracoes/webhooks.md). ## Credits[​](#credits "Direct link to Credits") Besides the plan, the organization can have **credits** in reais (1 credit = R$ 1): * **Plan allowance**: if the plan includes monthly credits, the month's usage (executions and Slack conversations, at price) is covered up to the allowance. The month is the one in the organization's time zone. * **Balance**: credits bought in Kobana's billing go into the organization's balance when the invoice is paid. The Kobana team can also post adjustments. Who pays for each use is decided before it starts: | Situation | Who pays | | ------------------------------------------------------ | ---------------------------------------------------------------------------------------- | | Courtesy in force | the courtesy | | Plan without a credit allowance | the plan (the price goes to the invoice) | | Plan with an allowance, within this month's allowance | the plan | | Allowance used up, or no plan, with a positive balance | the balance (the use's price is debited) | | None of the above | the use is refused (`403 ENTITLEMENT_REQUIRED`, reason `credits_exhausted` or `no_plan`) | The balance is only used once the allowance runs out: it is a reserve. The debit happens when the use's price is calculated; the balance can go negative if the last use costs more than what was left. At 80 % and 100 % of the allowance, the organization's owners get an email. ## Tiers[​](#tiers "Direct link to Tiers") Each task has a **tier** (`Tier 1`, `Tier 2`…), chosen in the repository through the `task.model` field (`model: tier-3`). The tier defines three things: | The tier defines | What for | | ---------------- | ---------------------------------------------------------------------------------------------------- | | **Price** | how much the execution costs the organization, in reais per million tokens (input, output and cache) | | **Executor** | the program that runs the agent (Claude Code or Goose) | | **AI model** | the model used by the executor | Higher tiers use more capable models and cost more per token. An organization can have a tier price negotiated with Kobana. ## Cost and price of an execution[​](#cost-and-price-of-an-execution "Direct link to Cost and price of an execution") Each execution records the tokens consumed (input, output, cache read and write) and two values: | Value | Currency | What it is | | ----------------------- | -------- | ----------------------------------------------------------- | | **Cost** (`cost_usd`) | USD | what the execution consumed from the AI provider | | **Price** (`price_brl`) | BRL | tokens × tier price; the amount charged to the organization | The price uses the PTAX selling rate of the business day before the execution (`usd_brl_rate`). The execution's **Cost** tab and the [Analytics](https://docs.kadu.ia.br/en/en/console/analytics.md) screens show these numbers. ## Slack conversations[​](#slack-conversations "Direct link to Slack conversations") Each message answered by the [Slack bot](https://docs.kadu.ia.br/en/en/configuracoes/slack.md) is metered too: tokens, cost and price, calculated the same way as for an execution. Conversations have a single tier, set by Kobana for the bot, which also accepts a negotiated price. Conversations count in the same monthly usage, in the credit allowance, in the monthly cap and in [Analytics](https://docs.kadu.ia.br/en/en/console/analytics.md), with the **Slack conversations** source. Each conversation has a spending limit; once reached, the bot asks to start another thread. ## Monthly cap[​](#monthly-cap "Direct link to Monthly cap") Kobana can set a **monthly cost cap** for the organization, in USD. The month is the one in the organization's time zone: the cap reopens at midnight on the 1st in that time zone. The cost of Slack conversations adds up to the same cap. Once the cap is reached, new runs and new conversations are rejected until the month turns over (`cost_limit_reached` in the API) and your webhooks receive `organization.cost_limit_reached`. The cap applies even with an active plan. --- # Tasks and skills A **skill** is a `SKILL.md` file: natural-language instructions that the agent follows, with a YAML header (frontmatter). An organization's skills live in its [Kadu Playbook](https://docs.kadu.ia.br/en/en/playbook.md), and they also run in the terminal of whoever writes them ([Local development](https://docs.kadu.ia.br/en/en/playbook/desenvolvimento-local.md)). A skill becomes a **task** when it declares the `task:` block in the frontmatter. Without the block, it is a **helper**: other skills call it, but it does not appear in the console. | | Helper skill | Task | | ------------------------------------------- | ----------------------------------------- | ---------------------------------------------------- | | Frontmatter | `name`, `description`, `user-invocable` | the same, **plus the `task:` block** | | Who calls it | another skill, or a person in Claude Code | the console, a schedule, the API or the run endpoint | | Has a person answering during the execution | yes | **no** | | Appears in the console | no | yes | ## Task identifier[​](#task-identifier "Direct link to Task identifier") Every task has a `skill_slug` in the format `:`: the plugin name (the workspace) and the skill's folder name. For example, the skill `finance/skills/task-baixar-pagamentos-via-boleto-pix/SKILL.md` becomes the task `finance:task-baixar-pagamentos-via-boleto-pix`. The slug is the task's identity. Renaming the skill folder creates a **different** task, without the previous one's history. To change the displayed name, change `task.label`. ## What the task declares[​](#what-the-task-declares "Direct link to What the task declares") The `task:` block defines what the console shows and how the execution runs: | Field | Effect | | ----------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | | `label`, `description` | The task's name and introduction text in the console. | | `activity` | The activity under which the task appears. | | `model` | The task's **tier**: defines the price, the executor and the AI model. | | `parameters_schema` | The parameters accepted at run time, as JSON Schema. The console builds the form from it, and an invalid parameter is rejected before the execution starts. | | `accepts_input_files`, `generates_output_files` | Whether the task receives files at run time and whether it publishes files at the end. | | `required_secrets` | The secrets the execution receives. Only those listed reach the execution. | | `systems`, `features` | The systems the task uses and what it delivers, shown in the console. | | `schedules`, `endpoint`, `executor`, `metadata` | Schedules, HTTP trigger, model override and free-form metadata. | The full format, field by field, is in [The `task:` block](https://docs.kadu.ia.br/en/en/playbook/bloco-task.md). ## No person on the other side[​](#no-person-on-the-other-side "Direct link to No person on the other side") A task execution is automatic: nobody answers a question from the agent. When the skill hits an ambiguity or a failure, it records a [pending item](https://docs.kadu.ia.br/en/en/console/pendencias.md) and moves on to the next item. Pending items stay in the console for a person to resolve later. --- # Schedules A **schedule** runs a task automatically in an environment, on a recurrence defined by a cron expression. Each task has at most one schedule per environment. ![Task schedules, one of them paused](/en/assets/images/configuracoes-agendamentos-96deb49c8e6cf54c09917d1123ca3900.png) ## Set up[​](#set-up "Direct link to Set up") With the task and the environment selected in the top bar, use **Set up schedule**, in the side panel of executions, and choose the frequency. The schedule applies to the selected environment. | Frequency | Example | | ------------------------ | --------------------------- | | On demand | no schedule | | Every X hours | every 4 hours, at minute 15 | | Daily | every day at 09:00 | | Weekdays (Mon–Fri) | Monday to Friday at 09:00 | | Weekly | every Monday at 09:00 | | Monthly | every 5th at 09:00 | | Custom (cron expression) | `0 9 * * 1-5` | :::caution Times in UTC A schedule created through the console uses **UTC** times. 09:00 UTC is 06:00 in Brasília. A schedule declared in the repository can use another time zone (`timezone: America/Sao_Paulo`). ::: ## Follow[​](#follow "Direct link to Follow") **Settings → Schedules** lists all of the organization's schedules: task, environment, recurrence and status (**Active** or **Paused**). From here you can: * **Pause** and **Resume**, without losing the configuration; * **Cancel schedule**, which removes the schedule. To schedule again, set it up again on the task. ## Rules[​](#rules "Direct link to Rules") * **It does not stack.** If the task already has an active or queued execution at the scheduled time, that run is skipped. A cron shorter than the task's duration loses runs. * **Secrets before the time.** The schedule runs with the environment's secrets. Register the value of a new secret before the next run time. * **Plan and cap.** Without a plan or with the monthly cap reached, the schedule does not fire (and webhooks receive the corresponding event). A skipped run is not redone. ## Schedules declared in the repository[​](#schedules-declared-in-the-repository "Direct link to Schedules declared in the repository") A skill can declare its own schedules (`task.schedules`). In that case, the repository is the source of truth: the recurrence and time zone revert to what was declared on the next push if they are edited here. A **pause** made here remains in effect as long as the skill does not declare `paused`. See [Schedule, endpoint and executor](https://docs.kadu.ia.br/en/en/playbook/agendamento-endpoint-executor.md). Through the API: `/v1/schedules`. --- # Environments **Settings → Environments** lists the organization's environments, with name, color and branch. The concept is in [Environments and secrets](https://docs.kadu.ia.br/en/en/conceitos/ambientes-e-secrets.md). ![Execution environments (Production and Staging)](/en/assets/images/configuracoes-ambientes-e7b1e25f5f35314fe12a386835000b81.png) ## Create and edit[​](#create-and-edit "Direct link to Create and edit") Click **New environment** and fill in: | Field | Rule | | ---------- | ------------------------------------------------------------------------------------- | | **Name** | unique within the organization. Schedules and `kadu.yaml` find the environment by it. | | **Color** | identifies the environment in the top bar. | | **Branch** | the branch used by executions in this environment. | ## Delete[​](#delete "Direct link to Delete") An environment with secrets or schedules cannot be deleted. Remove them first. ## Environments declared in the repository[​](#environments-declared-in-the-repository "Direct link to Environments declared in the repository") When the repository's `kadu.yaml` declares an environment, the repository is the source of truth: * the declared color and branch revert to the file's value on the next push if they are edited here; * an environment created by the repository disappears when it leaves the file (unless it still has secrets or schedules); * an environment created here is never deleted by the repository, even if the file stops mentioning it. See [The `kadu.yaml`](https://docs.kadu.ia.br/en/en/playbook/kadu-yaml.md). Through the API: `/v1/environments`. --- # Content **Settings → Content** shows what the Playbook created in the organization: each workspace, its activities and the tasks of each activity. ![Workspaces, activities and tasks coming from the Playbook](/en/assets/images/configuracoes-conteudo-9b3f9c8262120c2f19175f6abfdf219a.png) At the bottom are two lists that help you find what did not become a task: | List | What it contains | | ------------------------------------------- | ---------------------------------------------------------------------------------------------------- | | **Skills without a task** | skills of a plugin with a workspace that have no `task:` block: the helpers, called by other skills. | | **Skills from plugins without a workspace** | skills from Playbook plugins that do not yet correspond to a workspace in the organization. | The screen is read-only: workspaces, activities and tasks change through the repository. See [Sync](https://docs.kadu.ia.br/en/en/playbook/sincronizacao.md). --- # Deployment **Settings → Deployment** shows the Playbook linked to the organization: ![The Playbook and the commit in use](/en/assets/images/configuracoes-implantacao-1e7fdf294a449e698f7943150534b79c.png) | Field | What it is | | ----------------------- | --------------------------------------------------------------------- | | **Playbook repository** | the git repository with the plugins and skills. | | **Last sync** | when Kadu last read the repository. | | **Last sync error** | appears when the read failed. The previous catalog remains in effect. | | **Current commit** | the default-branch commit that Kadu read. | | **Latest commits** | date, description, author and hash of the most recent commits. | ## Sync[​](#sync "Direct link to Sync") Kadu reads the repository on its own on each push to the default branch and checks every hour whether anything changed. The **Sync** button forces the read right away, for example to see a task that has just been published. With no Playbook configured, the screen guides you to connect one. See [Connect the Playbook](https://docs.kadu.ia.br/en/en/playbook/conectar.md). ## Code and catalog[​](#code-and-catalog "Direct link to Code and catalog") The sync updates the **catalog**: workspaces, activities, tasks, environments, schedules and webhooks declared in the repository. The skills' **code** does not depend on it: each execution reads the repository's default branch at the moment it starts. A merge that changes only a skill's text takes effect on the next run. See [Sync](https://docs.kadu.ia.br/en/en/playbook/sincronizacao.md). Through the API: `GET /v1/repository` and `POST /v1/repository/sync`. --- # Endpoints A task with an **endpoint** can be triggered by another system with an HTTP `POST`, without going through the console and without the API token: each task has its own credential. ![Endpoint to trigger a task through the API](/en/assets/images/configuracoes-endpoints-4c0e75899007de53828426e16b8d006b.png) **Settings → Endpoints** lists the tasks with the endpoint enabled, each one with: * the **endpoint URL** (`https://api.kadu.ia.br/run-task/{task_id}`), with the **Copy** button; * the **authentication** type and whether the credential is stored; * a **call example**. ## Authentication[​](#authentication "Direct link to Authentication") | Type | How the call authenticates | | ----------------- | ---------------------------------------------------------- | | **HTTP Basic** | `Authorization: Basic` with user and password | | **Bearer Token** | `Authorization: Bearer ` | | **Header** | a header with a name and value you define (`X-Api-Key: …`) | | **URL parameter** | a query parameter with a name and value you define | caution Credentials in the URL can appear in proxy and server logs. Prefer Bearer Token or Header. In **Edit security**, change the credential. Leaving the field blank keeps the current credential. The credential is stored encrypted and is never shown again. ## Enable and disable[​](#enable-and-disable "Direct link to Enable and disable") The Playbook is what enables a task's endpoint (`task.endpoint`), and the first credential is registered by the Kobana team. If no task appears in the list, ask Kobana support. See [Schedule, endpoint and executor](https://docs.kadu.ia.br/en/en/playbook/agendamento-endpoint-executor.md). **Disable** makes calls receive `404` and keeps the security configuration. If the Playbook declares the endpoint as enabled, the next push turns it on again. ## The call[​](#the-call "Direct link to The call") The body, responses and errors are in [Run endpoint](https://docs.kadu.ia.br/en/en/api/overview/run-task-endpoint.md). The endpoint also receives Kobana's Autonomous Inbox webhook directly. --- # Secrets **Settings → Secrets** lists the organization's secrets: key, environment, workspace and the date of the last change. The value is never shown. The concept is in [Environments and secrets](https://docs.kadu.ia.br/en/en/conceitos/ambientes-e-secrets.md). ![Secrets per environment, with the pending ones highlighted](/en/assets/images/configuracoes-secrets-1e222c3a956a914f7dc776c643614cdd.png) Register an environment first: every secret belongs to one. ## Create[​](#create "Direct link to Create") Click **New secret** and fill in: | Field | Rule | | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | | **Key** | the name of the variable the skill reads, in `UPPER_SNAKE_CASE`, identical to what the task declares in `required_secrets` (`FINTERA_TOKEN`). | | **Value** | stored encrypted. | | **Environment** | where the secret applies. | | **Workspace** | optional: restricts the secret to the tasks of one workspace. | | **Notes** | free text for whoever maintains it (where the token came from, who owns it). | ## Edit[​](#edit "Direct link to Edit") To change the value, save a new one. Leaving the **Value** field blank keeps the current value, so you can change only the notes. ## Pending[​](#pending "Direct link to Pending") The top of the screen summarizes how many secrets are **pending**: required by tasks and with no value in the environment. Each one appears in the list with the **Set value** shortcut. ## Delete[​](#delete "Direct link to Delete") Tasks that use the key stop receiving it on the next execution. Through the API: `/v1/secrets`. The API never returns a secret's value. --- # Slack With the Kadu app installed in Slack, you talk to the agent in a thread: ask it to list Tasks, run one of them, follow progress, cancel or request a change. Status, to-dos, result and files of each execution arrive in the same thread. ## Install[​](#install "Direct link to Install") In **Settings → Slack**, click **Install in Slack** and authorize the app in the workspace. An organization can have more than one workspace installed. A Slack workspace is linked to a single organization. Whoever installs straight from Slack, without going through the console, gets a direct message from the bot with the **Connect Kobana Account** button. Signing in links Slack to your account's active organization (your role must allow editing the organization). Whoever has no organization on Kadu yet goes to sign-up. An installation not linked to any organization within 14 days is removed. ## Connect your Kobana Account[​](#connect-your-kobana-account "Direct link to Connect your Kobana Account") Every bot user uses their own Kobana Account. On the first message, the bot answers only to you with the **Connect Kobana Account** button: sign in and go back to Slack, and the bot picks up your request. * The connection lasts **7 days**. After that, the bot asks you to sign in again. On the last day, it warns you on the first message you send. * Access is your role in the organization, the same as in the console. Without an invitation to the organization, the bot does not answer. * To disconnect, write **disconnect** to the bot. * Removed from the organization or deactivated in Slack, the connection drops by itself. ## Run a Task[​](#run-a-task "Direct link to Run a Task") Ask in natural language: "run yesterday's payment settlement in the Production environment". The bot checks the Task and the parameters and posts a **confirmation card** with Task, environment, reference date, parameters and missing secrets. **Nothing runs until someone clicks Run.** The card is valid for 15 minutes. Whoever asked, or another member allowed to run, can confirm. If the connection of whoever clicked has expired, the click does not count: sign in and confirm again. Cancelling an execution and requesting a change on a conversational Task also go through a card. ## Input files[​](#input-files "Direct link to Input files") A Task that takes files (spreadsheet, statement, PDF) uses the attachments of the thread itself: attach the file in the conversation and ask for the run. The card shows the files that go with it. * Attachments of up to 100 MB. An external link (Google Drive, for example) does not count: attach the file itself. * Only files attached in the same thread and not yet used by another execution go in. Each file goes to a single execution, as in the console. * The attachment is stored only after your Kobana Account is connected. If you attach before connecting, the bot stores it after the connection. ## What shows up in the thread[​](#what-shows-up-in-the-thread "Direct link to What shows up in the thread") | Message | When | | -------------- | ------------------------------------------------------------------------------------ | | **Status** | a pinned message, edited with the current step, the elapsed time and the open to-dos | | **Milestones** | step completed, warning and error, each in its own message | | **To-do** | each new to-do, with the **Resolve** button | | **Result** | at the end: status, summary, duration and price | | **Files** | the output files, as a console link or attached (see below) | The execution goes on even if your connection expires midway: status, result and files keep arriving. ## Request a change[​](#request-a-change "Direct link to Request a change") On a conversational Task, reply in the thread after the execution ends: "redo it considering only the bank slips". The bot posts the change card; once confirmed, the new round goes on in the same thread. ## Configure[​](#configure "Direct link to Configure") Each installed workspace has three settings in **Settings → Slack**: | Setting | Default | What it does | | ------------------------- | ---------------------------------- | ----------------------------------------------------------------------------------------------------------- | | **Where the bot answers** | direct message and private channel | also talks in group conversations, public channels and channels shared with another company (Slack Connect) | | **Output files** | console link only | attaches in the thread up to the maximum size (up to 100 MB); above that, a link | | **Detail in the thread** | milestones only | also a summary of the execution logs | Public channels and Slack Connect expose results and files to more people: enable them only if the organization decided so. In a shared channel, people from another company never use the bot, even with a Kobana Account. A Task can pin the files mode in the Playbook, in `metadata.slack.files_mode` (`link` or `upload`) of the `task:` block. In a channel, mention the bot (`@Kadu`) to start. In a thread where it has already posted a card or an execution, there is no need to mention it again. ## Usage[​](#usage "Direct link to Usage") Each answered message is metered and charged like an execution: tokens × price of the conversations tier, in the same monthly usage, the same credit allowance and the same cap. Without a plan, allowance or balance, the bot warns and does not answer. Each conversation has a spending limit and each person can send up to 20 messages per minute. See [Plan and costs](https://docs.kadu.ia.br/en/en/conceitos/plano-e-custos.md). ## Remove[​](#remove "Direct link to Remove") **Remove** in **Settings → Slack** revokes the app in the workspace: the bot stops answering and account connections are undone. Uninstalling from Slack itself has the same effect. The same configuration is in the API: `GET`, `PATCH` and `DELETE` on `/v1/organizations/{id}/slack_installations`. --- # Webhooks A **webhook** sends a signed HTTP `POST` to your system when an event happens: an execution finished, failed, created a pending item. ![Webhooks that receive the execution events](/en/assets/images/configuracoes-webhooks-0526bb0a35dd34ce54ad01bd1e956cae.png) ## Create[​](#create "Direct link to Create") In **Settings → Webhooks**, click **New webhook**: | Field | Rule | | --------------------- | -------------------------------------------------------------------------------------------------------------------- | | **Name** | identifies the webhook. | | **URL** | public `https` address. Private, loopback or `http` addresses are rejected. | | **Workspace** | restricts to the events of one workspace. Blank: the whole organization. | | **Authentication** | how the delivery authenticates to your system: **Bearer token**, **Basic** (user and password) or **Custom header**. | | **Subscribed events** | the events that trigger the delivery. | When you save, the console shows the webhook's **signing secret** **only once**. Copy and store it: your system uses it to verify the signature. ## Events[​](#events "Direct link to Events") | Event | When | | ------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | | `execution.created` | an execution was created | | `execution.queued` | the execution joined the task's queue | | `execution.started` | the agent started | | `execution.completed` | finished successfully | | `execution.failed` | finished with a failure | | `execution.cancelled` | was cancelled | | `todo.created`, `todo.resolved` | a pending item was created or resolved | | `organization.cost_limit_reached` | the monthly cost cap was reached | | `organization.entitlement_required` | a schedule did not run for lack of a plan | | `task.db.*`, `activity.db.*`, `workspace.db.*`, `execution.db.*`, `memory.db.*` | the record was created, changed or removed (`created`, `updated`, `deleted`) | ## Deactivation after failures[​](#deactivation-after-failures "Direct link to Deactivation after failures") After 5 consecutive delivery failures, the webhook is **deactivated** and the people registered for notices receive an email. Fix the receiver and reactivate the webhook. ## Receive and verify[​](#receive-and-verify "Direct link to Receive and verify") The body format, the headers and signature verification are in [API webhooks](https://docs.kadu.ia.br/en/en/api/overview/webhooks.md). The delivery history and resend are in the API (`/v1/webhook_deliveries`). ## Webhooks declared in the repository[​](#webhooks-declared-in-the-repository "Direct link to Webhooks declared in the repository") `kadu.yaml` can declare webhooks. A webhook created by the repository is born without a credential and with a signing secret that nobody has seen: register the credential here and generate a new signing secret through the API (`POST /v1/webhooks/{id}/rotate-secret`). See [The `kadu.yaml`](https://docs.kadu.ia.br/en/en/playbook/kadu-yaml.md). --- # Follow an execution Choose an execution in the task's list to open its screen. While the execution runs, the screen updates on its own. ![Execution history: each Kadu step in the conversation](/en/assets/images/execucao-historico-7bd3a5aaad5adf3c46788097710f97c8.png) ## Tabs[​](#tabs "Direct link to Tabs") ![Settlement result: table of the reconciled payments](/en/assets/images/execucao-resultado-a13f0cd98cab092de046b10feb4716b3.png) ![Logs of the API calls made during the execution](/en/assets/images/execucao-logs-d6f224c94dc3e9b9c9546f16efaa2f69.png) ![Weekly cash flow report](/en/assets/images/relatorio-resultado-bf1ad5cd3dcfa245b117b1eeaf459c23.png) | Tab | What it shows | | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Details** | status, reference date, environment, elapsed time or duration, tier, tokens and cost. For finished executions, also the task statistics: average, minimum and maximum cost and the number of executions. | | **Timeline** | the execution timeline, step by step, with what the agent announced in each one (`Step 2 · Fetching yesterday's payments`). Warnings and errors are highlighted. A queued execution shows how many are ahead of it. | | **Logs** | the log lines and the HTTP calls to external systems (method, URL, status and time), with secrets masked. Copy or download the full logs. While the execution runs, the **Live logs** shortcut follows the end of the file. | | **Pending** | what the skill left for a person. See [Pending items](https://docs.kadu.ia.br/en/en/console/pendencias.md). | | **Files** | the input files attached to the run and the output files published by the execution, with preview and download. | | **Result** | the final report the skill wrote, in Markdown: what was done, what was skipped and why. | ## Conversation: requesting a change[​](#conversation-requesting-a-change "Direct link to Conversation: requesting a change") In **conversational** tasks (a video, a document, a report that a person reviews), the **Result** tab of a finished execution shows the **Conversation** card. Write what should change ("redo it with the calmer soundtrack and the logo at the end") and click **Request changes**. Each request opens a **new execution**, a child of the previous one, that continues from it: same parameters, reference date and environment, the same files and the context of what the agent already did. The new turn has its own timeline, logs, pending items and result, and the card lists the turns so far. Where the conversation appears in the console: * **Banner at the top of the execution**, on every tab: "Turn N of M in the conversation", the request that opened the turn, the **Previous turn** link and the **Request changes** shortcut, which leads to the card. * **Execution list**: later turns are nested under the first one, collapsed behind an arrow, with the total number of turns next to the date. Click the arrow to expand or collapse; the conversation of the open execution is already expanded. Each turn shows the request that opened it. * **Run now**: the task appears with the **Conversational** badge. * Only **done** or **failed** executions can be continued; a cancelled one cannot. * Any turn can be continued, not just the last one: continuing an old one opens a branch. * The turn goes through the same rules as a run: plan, cost cap and the task queue. * If the card warns that the session was not saved, run the task again. * If the card warns that the task changed executor, its tier changed since this run and the new executor cannot resume the saved session: run the task again. Through the API: `POST /v1/executions/{id}/continue`, with `{ "message": "…" }`. ## Replay[​](#replay "Direct link to Replay") A finished execution can be **replayed**: the replay plays the timeline again, in the order and at the pace it happened, with speed control. It helps you understand what the agent did without reading the logs line by line. ## Cancel[​](#cancel "Direct link to Cancel") A queued, pending or running execution can be cancelled with the **Cancel** button. It is interrupted and marked as **Cancelled**. What it already did in external systems (a settlement, an attachment, a message) **is not undone**. ![A running execution, with Kadu at work](/en/assets/images/execucao-em-andamento-69e6d90163173f634a87da3a5d2986be.png) ## When the execution fails[​](#when-the-execution-fails "Direct link to When the execution fails") ![Execution failed on a Pagar.me credential error](/en/assets/images/execucao-erro-4fc47c73cb52532165659dc46162998c.png) 1. Open the **Timeline** and look for the last step before the error. 2. Check the **Logs** near that point, especially the external system's HTTP response. 3. Read the **Result**: a well-written skill records where it stopped even when it fails. | Symptom | Likely cause | | -------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | | Failed right at the start, with no steps in the timeline | a secret with no value in the environment, or the task could not start. Check the environment's secrets. | | Ran for 60 minutes and failed | the execution hit the maximum time. Usually high volume or a step waiting for a response. | | Call to an external system with `401` in the logs | wrong or expired token. Update the secret in the environment. | | Many pending items created | data the skill could not resolve on its own. Look at each pending item. | Problems that come from the skill itself (a step missing from the timeline, a result showing only "success") are in [Common errors](https://docs.kadu.ia.br/en/en/playbook/erros-comuns.md), in the section for people who write skills. --- # Analytics The **Analytics** screen has three views of the organization's usage (executions and conversations with the [Slack bot](https://docs.kadu.ia.br/en/en/configuracoes/slack.md)), all with the same filters: | Filter | Options | | ------------------------- | ---------------------------------------------------------------------------------- | | Period | today, yesterday, last 7, 30 or 90 days, or a custom range (default: last 30 days) | | Workspace, Activity, Task | restrict to the chosen executions (conversations have no task: they are left out) | | Source | Console, Schedule, API, Endpoint, Manage and Slack conversations | | Group by | tier, workspace, activity, task or source | Dates follow the organization's time zone. ## Usage[​](#usage "Direct link to Usage") Tokens consumed per day: input, output, cache read and cache write. Switch between chart and table and download the CSV. ![Usage: executions per day and per task](/en/assets/images/analytics-uso-162ef44a2183c21a3b870afc970c45a9.png) ## Cost[​](#cost "Direct link to Cost") Price of executions and conversations per day, in reais, with the period total, the number of executions, the number of conversation turns, the average cost per use and the average cost per day. See [Plan and costs](https://docs.kadu.ia.br/en/en/conceitos/plano-e-custos.md) for the difference between cost and price. ![Cost of the executions in the period](/en/assets/images/analytics-custo-790fea1604b37e8cda50fe94d4d1f863.png) ## Logs[​](#logs "Direct link to Logs") All of the organization's executions in one table: date and time, workspace, activity, task, status, source, tokens and cost. Each row leads to the execution. ![List of executions with status, duration and cost](/en/assets/images/analytics-logs-6bdbbc98409887e0ff12d0d49f29a1e0.png) ## Through the API[​](#through-the-api "Direct link to Through the API") `GET /v1/usage` returns aggregated usage and cost (with `conversation_turns`), and `GET /v1/executions` and `GET /v1/executions/search` list and search executions. --- # Files The **Files** screen gathers the files of all the organization's executions: ![Input and output files of every execution](/en/assets/images/arquivos-0b87a40f8ede5b32a5f899a0b6bc9b16.png) ![Input and output files of an execution](/en/assets/images/execucao-arquivos-3f3606c06eb5e0afe353ded1dea7e12b.png) | Type | Where it comes from | | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------- | | **Input** | attached at run time, in the **Run now** modal, on tasks that accept files (up to 100 MB per file). The skill receives the path of each one. | | **Output** | generated by the execution, on tasks that publish files. Reports, spreadsheets, PDFs. | The list shows the name, type, size, task, execution and date, with search by name and filters by type and by period. Each file can be previewed in the console itself (text, images and PDF) or downloaded. The same files appear in the **Files** tab of the execution that used or generated them. ## Through the API[​](#through-the-api "Direct link to Through the API") | Operation | Endpoint | | -------------------- | --------------------------------------------------------------------------------------------- | | List | `GET /v1/files` | | Upload an input file | `POST /v1/files` (returns an upload URL) and, after the upload, `POST /v1/files/{id}/confirm` | | Download | `GET /v1/files/{id}/download` (returns a temporary URL) | See the reference in [Files](https://docs.kadu.ia.br/en/en/api/v1/arquivos), in the API section. note The [run endpoint](https://docs.kadu.ia.br/en/en/api/overview/run-task-endpoint.md) accepts only JSON: a task triggered through it does not receive input files. --- # Run a task With the task selected in the top bar, click **Run now**. ![A configured task that has not run yet](/en/assets/images/tarefa-sem-execucoes-08993f426b33d8c46a9c3fa8284f9821.png) ## What the modal asks for[​](#what-the-modal-asks-for "Direct link to What the modal asks for") ![Run now, with parameters and input files](/en/assets/images/executar-tarefa-ef1199d710c0ffa0f1f49492d6711d20.png) | Field | When it appears | What for | | --------------------------- | --------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | | **Environment** | always | where the execution runs. It comes from the top bar; with no environment chosen, the modal asks for one. | | **Reference date** | always | the date the skill uses as "today". Default: the current date. Useful to reprocess a past day. | | **Parameters** | when the task declares `parameters_schema` | the form is built from the schema: required fields, numbers, lists (one item per line) and JSON. | | **Input files** | when the task accepts files (`accepts_input_files`) | drag or select files, up to 100 MB each. They are available to the skill during the execution. | | **Additional instructions** | optional | free text sent to the agent along with the skill. E.g., "today process only the payments from Heliotek". | ## Pre-check[​](#pre-check "Direct link to Pre-check") Before enabling the **Confirm** button, the console checks the environment: * **Missing secrets:** if the task requires a secret with no value in the environment, the modal lists which ones, with the **Configure secrets** shortcut. * **Queue:** if the task already has executions ahead, the modal says how many and the button becomes **Add to queue**. The new execution starts on its own when the previous ones finish. * **Plan and cap:** without a plan, or with the monthly cap reached, the run is blocked. See [Plan and costs](https://docs.kadu.ia.br/en/en/conceitos/plano-e-custos.md). Parameters that do not pass `parameters_schema` are rejected before the execution is created. ## After confirming[​](#after-confirming "Direct link to After confirming") The execution appears at the top of the task's list and the agent starts within 3 minutes. A notice on the screen follows the run until the execution starts. Follow it in [Follow an execution](https://docs.kadu.ia.br/en/en/console/acompanhar-execucao.md). ## Resolve pending[​](#resolve-pending "Direct link to Resolve pending") In the **Pending** tab of an execution, the **Resolve pending** button triggers a special execution: instead of running the skill from scratch, the agent revisits the open pending items and closes the ones that can already be resolved. See [Pending items](https://docs.kadu.ia.br/en/en/console/pendencias.md). ## Other ways to run[​](#other-ways-to-run "Direct link to Other ways to run") * **Schedule**, to run on its own: [Schedules](https://docs.kadu.ia.br/en/en/configuracoes/agendamentos.md). * **API**, with a Kobana Account token: `POST /v1/tasks/{id}/run`. * **Run endpoint**, called by another system: [Endpoints](https://docs.kadu.ia.br/en/en/configuracoes/endpoints.md). --- # Navigating the Agent Console The Agent Console (`app.kadu.ia.br`) is organized around a selected task. ![Workspace picker, with the activities and tasks of each one](/en/assets/images/inicio-workspaces-718bc9fe356da0aa2f02a60220c9b4a6.png) ## Top bar[​](#top-bar "Direct link to Top bar") The top bar chooses the context for everything shown below it: ![Activities of the Financeiro workspace](/en/assets/images/inicio-atividades-402934046937b35779621d8162ce7e94.png) | Selector | What it chooses | | --------------------------------------- | --------------------------------------------------------------------------- | | **Organization** | the open organization (only appears for people who belong to more than one) | | **Environment** | where executions triggered from here will run | | **Workspace** → **Activity** → **Task** | the task in focus | With no task chosen, the home screen guides the choice: first the workspace, then the activity, then the task. Also in the top bar: * the indicator of the organization's **running executions**, with a shortcut to each one; * the **global search**, which searches executions, logs, events, results and pending items (at least 2 characters); * the shortcuts to **Files**, **Analytics** and **Settings** (the latter for people who can change the organization); * the **Kobana Apps** menu; * the user menu: profile, **Switch organization**, theme, policies and **Sign out**. ## Selected task[​](#selected-task "Direct link to Selected task") With a task chosen, the side panel presents the agent: what the task does, the **systems it operates** and **what it does** (the skill's `systems` and `features` fields), followed by the task's list of **executions**, from newest to oldest, with each one's reference date. ![Tasks of the Contas a receber activity](/en/assets/images/inicio-tarefas-2d54234a9fbc2445c7044f2c830e6ce1.png) The main area shows the chosen execution, with the tabs described in [Follow an execution](https://docs.kadu.ia.br/en/en/console/acompanhar-execucao.md), and the **Run now** button. ## Other screens[​](#other-screens "Direct link to Other screens") The top bar shortcuts appear once the Playbook is connected: ![Profile: theme and language](/en/assets/images/perfil-8fb385587e60bcaedb7c60d4b96887ea.png) ![A newly created workspace, still without activities](/en/assets/images/workspace-vazio-b423416b3dfcdeeeb3a32ec293f8a368.png) | Screen | What it shows | | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | | Files | the executions' input and output files ([Files](https://docs.kadu.ia.br/en/en/console/arquivos.md)) | | Analytics | token usage, cost and the list of all executions ([Analytics](https://docs.kadu.ia.br/en/en/console/analytics.md)) | | Settings | content, environments, secrets, schedules, endpoints, webhooks and deployment ([Settings](https://docs.kadu.ia.br/en/en/configuracoes/conteudo.md)) | --- # Pending items A **pending item** is what the execution left for a person: a payment that did not match any account, a registration with a mismatched CNPJ, an invoice that was not found. Since nobody answers the agent during the execution, instead of stopping to ask, it records the pending item and moves on to the next one. ## Where to see them[​](#where-to-see-them "Direct link to Where to see them") ![A to-do opened by Kadu for review](/en/assets/images/execucao-pendencias-0f1410540d46c252e6f0c1ae05c617e0.png) * In the **Pending** tab of each execution, with the description of the problem and the context the skill recorded. * The execution header shows how many pending items are still unresolved. * Through the API, at `GET /v1/executions/{id}/todos`. ## Resolve[​](#resolve "Direct link to Resolve") After handling the problem in the source system, mark the pending item as done (**mark as done**). If it becomes valid again, **mark as pending** reopens it. Each pending item stores when it was created and when it was resolved. ![A to-do to update the Pagar.me key](/en/assets/images/execucao-erro-pendencias-4f4c154dac41ab5e516d392781503d40.png) Through the API: `PATCH /v1/todos/{id}`. ## Resolve pending[​](#resolve-pending "Direct link to Resolve pending") The **Resolve pending** button, in the **Pending** tab of an execution, triggers a special execution. The agent revisits the task's open pending items, checks in the systems whether the problem has already been resolved and closes the ones it can. Those that remain open stay as they were. Use it after fixing a batch of records, for example, so you do not have to mark them one by one. --- # Docs MCP This documentation has **its own MCP server**. It lets an AI assistant (Claude, ChatGPT, Cursor, VS Code, Claude Code) search and read the pages of this site without you copying and pasting anything, so answers about Kadu come from the published content, in the language you choose. ``` https://docs.kadu.ia.br/mcp ``` > **Not the product's MCP:** The Docs MCP only **reads the documentation**. It does not access your account, does not see tasks, executions or secrets, and changes nothing. For an agent to operate Kadu, use the **product MCP** (`mcp.kadu.ia.br`), which requires sign-in and follows your permissions — see [MCP › Clients](https://docs.kadu.ia.br/en/en/mcp/overview/clients.md). ## How it works[​](#how-it-works "Direct link to How it works") * **Public and read-only:** no sign-in, no token. It exposes the same content anyone sees on the site. * **Always current:** it reads the published version of the documentation. A new or changed page shows up in the MCP as soon as the site is published. * **Three languages:** Portuguese (`pt`), English (`en`) and Spanish (`es`). Without a language, it uses Portuguese. * **Transport:** Streamable HTTP, sessionless. Any MCP client that connects to a remote server by URL works. ## Tools[​](#tools "Direct link to Tools") | Tool | What it does | Parameters | | ------------- | ---------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | | `search_docs` | Searches pages by keywords in the title, path and summary, ignoring accents and case. Returns the best pages with path and summary | `query` (required), `locale`, `limit` (1 to 25, default 8) | | `get_page` | Returns the full content of a page as Markdown | `path`: the path returned by `search_docs` or `list_pages` (e.g. `/en/primeiros-passos`) or the full URL | | `list_pages` | Lists every page, with path and summary | `locale` | The assistant decides on its own when to call each tool. It usually searches with `search_docs` and then reads the relevant pages with `get_page`. ## Installing[​](#installing "Direct link to Installing") The quickest way is the **Copy page** menu at the top of every page of this site. Under **Docs MCP** you'll find: * **Connect to Cursor** and **Connect to VS Code:** install the server in one click. * **Copy MCP config:** copies the JSON block for clients configured by file. * **Copy MCP command:** copies the install command for the terminal. If you'd rather configure it by hand, the steps for each client are below. ### Any client, from the terminal[​](#any-client-from-the-terminal "Direct link to Any client, from the terminal") [`add-mcp`](https://www.npmjs.com/package/add-mcp) detects the clients installed on your machine and adds the server to them: ``` npx add-mcp "https://docs.kadu.ia.br/mcp" ``` ### Claude Code[​](#claude-code "Direct link to Claude Code") ``` claude mcp add --transport http kobana-kadu-docs https://docs.kadu.ia.br/mcp ``` ### Cursor[​](#cursor "Direct link to Cursor") Use **Connect to Cursor** in the page menu, or add this to `~/.cursor/mcp.json` (all projects) or the project's `.cursor/mcp.json`: ``` { "mcpServers": { "kobana-kadu-docs": { "url": "https://docs.kadu.ia.br/mcp" } } } ``` ### VS Code[​](#vs-code "Direct link to VS Code") Use **Connect to VS Code** in the page menu, or create `.vscode/mcp.json` in the project. In VS Code the key is `servers`, not `mcpServers`: ``` { "servers": { "kobana-kadu-docs": { "type": "http", "url": "https://docs.kadu.ia.br/mcp" } } } ``` ### Claude and ChatGPT[​](#claude-and-chatgpt "Direct link to Claude and ChatGPT") In **Settings › Connectors**, add a custom connector and paste the URL `https://docs.kadu.ia.br/mcp`. There is no sign-in. ### Other clients configured by file[​](#other-clients-configured-by-file "Direct link to Other clients configured by file") ``` { "mcpServers": { "kobana-kadu-docs": { "type": "http", "url": "https://docs.kadu.ia.br/mcp" } } } ``` ## Example questions[​](#example-questions "Direct link to Example questions") With the server connected, just ask. The assistant checks the documentation before answering: * "How do I schedule a task to run every day at a fixed time?" * "How does a task get the secrets it needs, and where do I register them?" * "How is the Kadu Playbook structured, and how do I write a skill's `task` block?" * "Write an example API call to trigger a task through its endpoint, based on the documentation." ## Testing without a client[​](#testing-without-a-client "Direct link to Testing without a client") ``` curl -s https://docs.kadu.ia.br/mcp \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"search_docs","arguments":{"query":"webhook","locale":"en"}}}' ``` ## Limits[​](#limits "Direct link to Limits") * The per-endpoint API reference pages (`/api/v1/...`) are not in the MCP. The machine-readable source for the API is the OpenAPI spec, in [API › OpenAPI](https://docs.kadu.ia.br/en/en/api/overview/openapi.md). * Search is by keywords, not semantic. If nothing comes up, try other terms or ask the assistant to use `list_pages`. * You can also read any page as Markdown by appending `.md` to its URL, or download the whole documentation at [`llms-full.txt`](https://docs.kadu.ia.br/en/llms-full.txt). --- # Authentication The MCP server accepts a **Bearer token** in the `Authorization` header, and nothing else — no cookie, no client certificate. :::warning Never in the URL A token in the query string (`?access_token=…`, or parameters such as `token`, `bearer`, `api_key` and `key`) **does not authenticate**, even next to a valid header: the request gets `401` with `error="invalid_request"` in `WWW-Authenticate`. A token in the URL leaks into proxy and CDN logs, the `Referer` and history — the MCP authorization spec and RFC 6750 forbid it. ::: That token is an **access token issued by the Kobana Authorization Server** (`https://account.kobana.com.br`), a JWT signed with `RS256` and issued **for this resource**: its `aud` is `https://mcp.kadu.ia.br` (in staging, `https://mcp.staging.kadu.ia.br`), and only that — a token with more than one audience is refused. The token arrives one of two ways — the OAuth flow, run by the client itself, or a **personal token** you create in the Kobana Account — and is verified the same way in both cases, with the same local signature check as the [v1 API](https://docs.kadu.ia.br/en/en/api/overview/authentication.md). ``` Authorization: Bearer eyJhbGciOiJSUzI1NiIs… ``` :::info Each surface has its own audience A token issued for the MCP **is not valid** on the API (`api.kadu.ia.br`), and an API token is not valid here. That keeps a token handed to an assistant from being reused as a REST credential — and the reverse. ::: ## Two ways to connect[​](#two-ways-to-connect "Direct link to Two ways to connect") | | OAuth flow | Personal token | | ---------------------- | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------- | | How you get it | Paste the URL into the client; it opens the Kobana Account sign-in and you authorize | Create it in the Kobana Account token wizard, with **MCP** usage, the resources and access levels, and the validity | | How the client uses it | On its own, after authorization, renewing it when it expires | You paste it into the client as `Authorization: Bearer ` | | Scope | The resources and access levels you kept checked on the consent screen | The resources and access levels you checked | | Good for | Claude, ChatGPT, IDEs and any client with OAuth | Clients without OAuth support, scripts and automations | The personal token must be created with **MCP** usage. A token created for the API carries the API audience and is refused here with `401` — and the reverse holds too. ## The OAuth flow[​](#the-oauth-flow "Direct link to The OAuth flow") You run none of these steps: the MCP client does everything on its own from the URL. They are here for when something fails along the way. 1. **Challenge.** A request without a token gets `401` with the challenge every OAuth 2.1 resource server must send ([RFC 9728](https://datatracker.ietf.org/doc/html/rfc9728)): ``` WWW-Authenticate: Bearer realm="kadu-mcp", error="invalid_token", resource_metadata="https://mcp.kadu.ia.br/.well-known/oauth-protected-resource" ``` 2. **Resource metadata.** The client reads the document it points to. It is public and carries no secret — only pointers: ``` curl https://mcp.kadu.ia.br/.well-known/oauth-protected-resource ``` ``` { "resource": "https://mcp.kadu.ia.br", "authorization_servers": ["https://account.kobana.com.br"], "bearer_methods_supported": ["header"], "scopes_supported": [ "kadu/activities", "kadu/environments", "kadu/executions", "kadu/files", "kadu/memberships", "kadu/memories", "kadu/organizations", "kadu/repository", "kadu/roles", "kadu/schedules", "kadu/secrets", "kadu/system_events", "kadu/tasks", "kadu/todos", "kadu/usage", "kadu/users", "kadu/webhook_deliveries", "kadu/webhooks", "kadu/workspaces", "read", "write", "delete", "import", "export", "all" ], "resource_documentation": "https://api.kadu.ia.br/v1/docs" } ``` `kadu/all` is left out on purpose: the catch-all is not handed to a client that registered itself. 3. **Authorization Server metadata** ([RFC 8414](https://datatracker.ietf.org/doc/html/rfc8414)), at `https://account.kobana.com.br/.well-known/oauth-authorization-server`. 4. **Dynamic client registration** ([RFC 7591](https://datatracker.ietf.org/doc/html/rfc7591)). The client registers itself. 5. **Authorization with PKCE and `resource`** ([RFC 8707](https://datatracker.ietf.org/doc/html/rfc8707)). You sign in to the Kobana Account, pick the organization (if you have more than one), review what the client asks for and authorize. 6. **Token.** The client exchanges the code for a short-lived access token and sends it on every request, renewing it when it expires. The token carries who you are (`sub`) and the organization you authorized (`org`). The `org` scopes the data — no tool takes an organization as an argument. A token whose organization does not exist in Kadu gets `403 unknown_organization`. ## Authorization: the role of who authorized[​](#authorization-the-role-of-who-authorized "Direct link to Authorization: the role of who authorized") What a caller sees is **narrower** than the catalog: tools are filtered by the **role of the token's person** in the organization — the same `agent..` permissions as the console and the API (see [Permissions](https://docs.kadu.ia.br/en/en/api/overview/permissions.md)). A tool requires exactly the permission of the route it mirrors. The ceiling is always what that person can do in the console. | Situation | Result | | ------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------- | | The token's person is not a member of the organization | **Denies every tool** (`not_a_member`). Authorize again with an account that belongs to the organization. | | Member | The role's permissions, applied tool by tool. The wildcards `*`, `agent.*` and `agent..*` work as in the console. | | The permission lookup fails | **Denies every tool.** Fails closed. | Unlike the API, the MCP has no integration token without a person: the token must lead to a person of the organization (by `sub` or by the token's e-mail), otherwise no tool runs. Permissions are re-read at least once a minute, so changing or removing someone's role takes effect within a minute, without waiting for the token to expire. A tool the caller cannot run **is not listed** in `tools/list`. When `tools/call` refuses, the code says **who fixes it**: | Code | Who fixes it | | ------------------------- | --------------------------------------------------------------------------------------------------------------------------- | | `insufficient_scope` | You, authorizing again with the missing access — or using a personal token that reaches the resource at the required level. | | `insufficient_permission` | An administrator of the organization, granting the permission (the error names it). | | `not_a_member` | Authorizing again with an account that is a member of the organization. | :::tip Recommended posture for agents Authorize the client with an account that has only the permissions the agent needs. A client authorized by someone who can only read exposes only the read tools, whatever the model on the other side does. ::: ## Token scope[​](#token-scope "Direct link to Token scope") On top of the role, the MCP enforces the scope the token carries, in the `scope` claim, with the same grammar as the API (see [API authentication → Scopes](https://docs.kadu.ia.br/en/en/api/overview/authentication.md#scopes)). The two checks add up: a tool appears only if both allow it. | Tool | Requires | | --------------------------------------------------------------------------------------- | --------------------------------- | | Read (`*_list`, `*_get`, `kadu_usage_get`…) | The resource scope (`kadu/tasks`) | | Delete (`*_delete`) | The resource scope + `delete` | | Any other change (create, update, run, cancel, continue, sync, replay, rotate a secret) | The resource scope + `write` | | `kadu_me_get` | Any valid token | The resource is the first segment of the route the tool mirrors: `kadu_executions_logs_list` belongs to `executions`, and `kadu_tasks_run` requires `kadu/tasks write`. Approvals by link (`kadu_operation_approvals_*`) are read with `kadu/system_events`. No level replaces another: `write` does not delete. No tool requires `import` or `export`. | Token scope | Result | | --------------------------------------- | --------------------------------------------------------------------------------------------------- | | `kadu/executions kadu/todos read write` | Reads and changes executions and pending items — cancels, continues and resolves — and nothing else | | `kadu/tasks kadu/executions write` | Reads, runs and follows tasks, without deleting anything | | `read` (no `kadu/…` at all) | Reads everything, writes nothing | :::info Clients connected through OAuth ask for every level — you choose which to grant The `/.well-known/oauth-protected-resource` document announces Kadu's resources and the six access levels, and that is what the client asks for on the consent screen. There you uncheck what you do not want to grant: the issued token carries only the resources and levels left checked, and the MCP lists only the tools they reach. A tool outside it returns `insufficient_scope`, and the way out is to authorize again granting the missing access — `delete` for the delete tools, for example. ::: ### Revoking a personal token[​](#revoking-a-personal-token "Direct link to Revoking a personal token") Kadu verifies the token on its own, by its signature, without asking the Kobana Account on every call: revoking a personal token there **does not cut access immediately**, and it stays accepted until it expires. So: * pick the **shortest validity** the use allows; * check only the **resources and access levels** the agent needs; * to cut an agent off immediately, remove the person's membership in the organization or change their role — it takes effect within a minute. ## Failure modes[​](#failure-modes "Direct link to Failure modes") | HTTP | `code` | Cause | | ---- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | 400 | `-32022` (JSON-RPC) | Unsupported protocol version, in the `MCP-Protocol-Version` header or in `_meta`; `data.supported` lists the supported ones. See [Transport](https://docs.kadu.ia.br/en/en/mcp/overview/transport.md#protocol-versions). | | 401 | `invalid_token` | Token missing, malformed, invalid, issued for another resource (the API, for example) or with more than one audience in `aud`. | | 401 | `token_expired` | The token expired. The client renews it and tries again. | | 401 | `token_revoked` | The token was revoked in Kadu. | | 401 | `invalid_request` | Token in the query string — see the warning at the top of the page. | | 403 | `unknown_organization` | The token is valid, but the organization it names does not exist in Kadu. | | 403 | `FORBIDDEN` | `Origin` outside the allowed list (`origin_not_allowed`). | | 404 | `NOT_FOUND` | The request arrived through a host that does not serve MCP. | | 413 | `-32600` (JSON-RPC) | Body above 1 MB. | | 429 | `TOO_MANY_REQUESTS` | Rate limit reached, with `Retry-After` and `X-RateLimit-*` — see [Rate Limiting](https://docs.kadu.ia.br/en/en/mcp/overview/rate-limit.md). | | 503 | `SERVICE_UNAVAILABLE` | We could not verify the token against the Authorization Server. It is on our side: try again after `Retry-After`. | Every `401` carries the `WWW-Authenticate` challenge above, which sends the client back to the OAuth flow. The challenge always says `error="invalid_token"` (or `invalid_request`, for a token in the URL), as RFC 6750 requires; the body `code` tells an expired token (`token_expired`) apart from the rest. Attempts with a refused token count against a per-IP limit, and past it the answer becomes `429`. No MCP response is cacheable: all of them, refusals included, carry `Cache-Control: no-store` and `Content-Security-Policy: default-src 'none'`. The exception is the public `/.well-known/oauth-protected-resource` document, with `Cache-Control: public, max-age=300`. Scope and permission denials are **not** HTTP errors: they come back as a tool result marked `isError`, so the model reads and reacts to them — see [Errors](https://docs.kadu.ia.br/en/en/mcp/overview/errors.md). ## Host and edge[​](#host-and-edge "Direct link to Host and edge") The MCP answers only through `mcp.kadu.ia.br` — never through the console host, never through the API host, which point at the same application. Through any other path the answer is `404`: the endpoint does not announce that it exists elsewhere. The OAuth resource identifier — and therefore the audience the token must carry — comes from the **server configuration**, never from the request's `Host`, so a forged `Host` cannot point a client at another identifier. --- # Clients Any client that speaks **Streamable HTTP** MCP and can run the MCP **OAuth 2.1** flow can connect. You only give it the URL; the client discovers where to authenticate, opens the Kobana Account sign-in in the browser and stores the token — see [Authentication](https://docs.kadu.ia.br/en/en/mcp/overview/authentication.md). A client without OAuth support can connect too, with a **personal token**: create it in the Kobana Account token wizard, with **MCP** usage, and configure the client to send it in the `Authorization: Bearer ` header. ## Claude Code[​](#claude-code "Direct link to Claude Code") ``` claude mcp add --transport http kadu https://mcp.kadu.ia.br ``` Then run `/mcp` in a session and choose **Authenticate** to sign in to the Kobana Account. With a personal token, without the OAuth flow: ``` claude mcp add --transport http kadu https://mcp.kadu.ia.br \ --header "Authorization: Bearer " ``` ## File-configured clients[​](#file-configured-clients "Direct link to File-configured clients") Clients that read a JSON configuration file ask for the URL and the transport. With the OAuth flow, there is no authentication header: ``` { "mcpServers": { "kadu": { "type": "http", "url": "https://mcp.kadu.ia.br" } } } ``` On the first connection the client opens the browser for you to authorize. There is no organization-selection header: you pick the organization on the consent screen, and it is stored in the token. With a personal token, add the header — the organization is the one of the account where you created the token: ``` { "mcpServers": { "kadu": { "type": "http", "url": "https://mcp.kadu.ia.br", "headers": { "Authorization": "Bearer " } } } } ``` Treat the file as a secret: whoever reads it uses the token. ## Clients that connect by URL[​](#clients-that-connect-by-url "Direct link to Clients that connect by URL") Claude and ChatGPT connect a remote server by pasting the URL `https://mcp.kadu.ia.br` into their connector settings. The OAuth flow runs inside the app itself. Clients that show no confirmation form (the Claude mobile app, for example) also run destructive tools: the assistant shows an approval link, and you decide in the console — see [Confirming destructive tools](https://docs.kadu.ia.br/en/en/mcp/overview/resources-and-prompts.md#confirming-destructive-tools). ## Recommended posture for agents[​](#recommended-posture-for-agents "Direct link to Recommended posture for agents") * **Authorize with the right account.** Tools are narrowed to the role of who authorized the client. A client authorized by someone who can only read exposes only the read tools, whatever the model on the other side does. It is the strongest way to restrict an agent. * **Grant only what the agent needs.** On the consent screen, uncheck the levels it does not use: without `delete`, no delete tool appears. * **One token per client.** Each client runs its own flow and gets its own token, which is valid only on the MCP — it cannot call the API. * **Personal token: short validity and minimal scope.** Revoking the token in the Kobana Account does not cut access before it expires — see [Revoking a personal token](https://docs.kadu.ia.br/en/en/mcp/overview/authentication.md#revoking-a-personal-token). * **Start with a test environment.** `kadu_tasks_run` runs the agent for real, against the systems the environment's secrets point to. The server asks for confirmation before every run, but the first exploration should be in an environment whose secrets point to sandboxes. * **Results are data.** Events, logs, memories and pending items carry text written from third-party data. Do not follow directions found there, and confirm before any action a result appears to ask for. ## Checking the connection[​](#checking-the-connection "Direct link to Checking the connection") The simplest way is to ask the assistant: "which organization am I using in Kadu?" — it calls `kadu_me_get`. With a token issued for the MCP — a personal token with **MCP** usage, for example — you can check with `curl`: ``` # 1. Which organization does this token reach? curl -s https://mcp.kadu.ia.br/ \ -H "Authorization: Bearer $MCP_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/call", "params":{"name":"kadu_me_get","arguments":{}}}' # 2. How many tools does this token reach? curl -s https://mcp.kadu.ia.br/ \ -H "Authorization: Bearer $MCP_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' | jq '.result.tools | length' ``` If the number is lower than you expected, it is the role of who authorized or the token's scope — see [Authentication](https://docs.kadu.ia.br/en/en/mcp/overview/authentication.md). It is not an error. --- # Endpoints ## MCP server[​](#mcp-server "Direct link to MCP server") | Environment | URL | | ----------- | -------------------------------- | | Production | `https://mcp.kadu.ia.br` | | Staging | `https://mcp.staging.kadu.ia.br` | The server URL is the **host root** — that whole address is what you paste into an MCP client. It is also the OAuth *resource identifier* (the token's audience), which is why it carries no path. ## Routes served[​](#routes-served "Direct link to Routes served") | Method | Path | What for | | -------- | --------------------------------------- | ------------------------------------------------------------------------------------------ | | `POST` | `/` | One JSON-RPC message per request. The whole surface of tools, resources and prompts. | | `GET` | `/.well-known/oauth-protected-resource` | Protected resource metadata (RFC 9728). Public, no authentication. | | `GET` | `/` | `405` (after authentication) — this server is stateless and opens no server→client stream. | | `DELETE` | `/` | `204` (after authentication) — there is no session to end. | On the MCP host, any other path answers `404` in JSON, with any method: ``` { "error": { "code": "NOT_FOUND", "message": "Not found." } } ``` ## Docs, console and API[​](#docs-console-and-api "Direct link to Docs, console and API") | | URL | | -------------------------- | --------------------------------------------------------- | | Documentation | | | Agent Console (production) | — where the approval link points | | Agent Console (staging) | | | v1 API (production) | `https://api.kadu.ia.br/v1` | | v1 API (staging) | `https://api.staging.kadu.ia.br/v1` | tip Each environment has its own audience: a token issued for the staging MCP is not valid on production, and vice versa. Do not reuse a token across environments. :::info A separate host, on purpose The MCP server has its own host instead of a path on the API or console host. There are three reasons: the OAuth discovery document has to live at the host root; no console session cookie is ever sent to this origin, which removes a whole class of *confused deputy* problems by construction; and the MCP token has its own audience, which cannot be mistaken for the API's. ::: ## Local development[​](#local-development "Direct link to Local development") In development the endpoint answers at `/mcp` on the app host (`http://localhost:3030/mcp`), and the discovery document at `/.well-known/oauth-protected-resource`. Outside development, `/mcp` answers only when the request comes through the MCP host's edge; through the console or API host, the answer is `404`. --- # Errors Failures arrive through **two different channels**, and the distinction matters: one is a broken request, the other is something the model should read and react to. ## Tool errors[​](#tool-errors "Direct link to Tool errors") Everything the caller can act on — an invalid argument, not found, insufficient scope or permission, a route error — comes back as a **successful JSON-RPC response** whose result is marked `isError`, carrying the API's own error envelope as text: ``` { "jsonrpc": "2.0", "id": 2, "result": { "isError": true, "content": [{ "type": "text", "text": "{ \"error\": { \"code\": \"NOT_FOUND\", \"message\": \"Execution not found\" } }" }] } } ``` The `code` vocabulary is the [v1 API's](https://docs.kadu.ia.br/en/en/api/overview/errors.md), unchanged. The ones you will meet most: | `code` | When | | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `VALIDATION_ERROR` | The arguments failed validation — the tool's own (`Invalid tool arguments.`) or the route's; the details name the fields. | | `NOT_FOUND` | The record does not exist, was removed or belongs to another organization. | | `CONFLICT` | State conflict — for example, an environment name already in use. | | `IDEMPOTENCY_KEY_REUSED`, `IDEMPOTENCY_REQUEST_IN_PROGRESS` | See [Idempotency](https://docs.kadu.ia.br/en/en/mcp/overview/idempotency.md). | | `FEATURE_NOT_ENABLED` | Kobana has not enabled this operation for the organization. | | `ENTITLEMENT_REQUIRED` | A run without Kadu in the organization's plan. | | `MISSING_SECRETS`, `ENVIRONMENT_REQUIRED` | A run with required secrets missing a value in the environment, or without a valid environment. `kadu_tasks_readiness_get` checks beforehand. | | `COST_LIMIT_REACHED` | The organization reached its monthly cost limit. | | `RATE_LIMITED` | The route's limit was reached — see [Rate Limiting](https://docs.kadu.ia.br/en/en/mcp/overview/rate-limit.md). | | `USER_DECLINED` | In era `2026-07-28`, you declined or cancelled the confirmation of a destructive tool. Nothing changed. | | `confirmation_required` | The tool is destructive, the client cannot confirm in the chat and the token has no person of the organization who could open an approval link. Nothing ran. | | `confirmation_replayed` | The confirmation sent had already been used — each one runs a call once. Call the tool again to be asked again. Nothing ran. | | `confirmation_unavailable` | The server could not record the confirmation (or the approval by link) right now, so it refused the tool. Nothing ran; try again later. | And the three the MCP itself adds, because they are about *authorization*, not about the record: | `code` | Who fixes it | | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `insufficient_scope` | The token's scope does not reach the tool: the resource is not in the token, or the level it requires (`write` or `delete`) is missing. Authorize again granting the missing access, or use a personal token that reaches the resource at the required level. | | `insufficient_permission` | An administrator of the organization, granting the permission the error names. | | `not_a_member` | Authorizing the client again with an account that is a member of the organization — no permission fixes this. | These three are separate **on purpose**: they are different conversations, with different people. A single error would say "you can't" without saying whom to ask. See [Authentication](https://docs.kadu.ia.br/en/en/mcp/overview/authentication.md). ### Not everything that does not run is an error[​](#not-everything-that-does-not-run-is-an-error "Direct link to Not everything that does not run is an error") * **`input_required`.** In era `2026-07-28`, the first `tools/call` of a destructive tool may return `resultType: "input_required"` with a confirmation request for the user. The client shows the question and re-sends the same call with the answer. * **`approval_required`.** In other clients, the same call returns, without `isError`, `status: "approval_required"` with the approval link. Show the link; do not call the tool again. Both are the normal flow — see [Confirming destructive tools](https://docs.kadu.ia.br/en/en/mcp/overview/resources-and-prompts.md#confirming-destructive-tools). ## Protocol errors[​](#protocol-errors "Direct link to Protocol errors") A malformed message gets a JSON-RPC error object, not a result: | Code | Meaning | | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | | `-32700` | Parse error — the body is not valid JSON (HTTP `400`) | | `-32600` | Invalid request — not a JSON-RPC object, an array (batching is not supported) or a body above 1 MB | | `-32601` | Method not found (HTTP `404` in era `2026-07-28`) | | `-32602` | Invalid params — unknown tool or prompt, missing `name`, invalid prompt argument, resource without permission; in era `2026-07-28`, also resource not found | | `-32002` | Resource not found — 2025 era only (`2026-07-28` uses `-32602`) | | `-32603` | Internal error — unexpected failure running the tool | | `-32020` | Header mismatch — in era `2026-07-28`, an MCP header does not match the body (HTTP `400`) | | `-32022` | Unsupported protocol version (HTTP `400`) | ``` { "jsonrpc": "2.0", "id": 3, "error": { "code": -32601, "message": "Method not found: tools/describe" } } ``` ## Transport errors[​](#transport-errors "Direct link to Transport errors") Refusals that happen **before** the JSON-RPC message is read are plain HTTP responses, with the `{ "error": { "code", "message" } }` envelope: missing or invalid token, unknown organization, `Origin` outside the list, rate limit, body too large, token verification unavailable. They are listed in [Authentication → Failure modes](https://docs.kadu.ia.br/en/en/mcp/overview/authentication.md#failure-modes). ## Why `isError` and not a JSON-RPC error[​](#why-iserror-and-not-a-json-rpc-error "Direct link to why-iserror-and-not-a-json-rpc-error") A `404` on an execution is not a defect of the MCP client — it is information about the world, and the model on the other side needs it to correct itself. Burying it in a protocol error would make the client treat it as a transport failure, hide the text and, at best, retry the identical call. Hence the rule: **if the caller can act on it, it is a result; if the request is broken, it is an error.** ## Observability[​](#observability "Direct link to Observability") Each `tools/call` writes one structured log line with the tool, the organization, the token id, the token kind (OAuth flow or personal), the status and the duration — never the arguments, the token or the body. What the tool does goes through the same API route, with the same effects: a run becomes an execution like any other, with the same events and webhooks. Approvals by link are recorded in **Settings › Approvals**, with what was asked, the decision, when and the result. --- # Idempotency The tools that **change data** — all of them except the read and delete tools — accept an optional `idempotency_key` argument. It is sent to the route as the `X-Idempotency-Key` header the v1 API already honors, and the rules are the API's (see [API idempotency](https://docs.kadu.ia.br/en/en/api/overview/idempotency.md)). ``` { "name": "kadu_tasks_run", "arguments": { "id": "2f1c…", "environment_id": "8a40…", "reference_date": "2026-10-30", "idempotency_key": "run-2026-10-30-boleto-payments" } } ``` | | | | ------------------------------------------- | ------------------------------------------------------------------------------------------------ | | **Format** | Opaque text, 1 to 255 characters. | | **Validity** | 24 hours, within the organization. | | **Repeat with the same arguments** | Returns the original response, without running the operation again. | | **Repeat with different arguments** | `isError` with `IDEMPOTENCY_KEY_REUSED`. | | **Repeat while the first is still running** | `isError` with `IDEMPOTENCY_REQUEST_IN_PROGRESS`; try again in a few seconds, with the same key. | The repeat returns the same body as the first call. The API's `X-Idempotent-Replay` header does not reach the tool result, which carries only the body. A secret shown only once — the signing secret from `kadu_webhooks_create` and `kadu_webhooks_rotate_secret` — comes back `null` on the repeat. ## Why an agent needs this more than your backend[​](#why-an-agent-needs-this-more-than-your-backend "Direct link to Why an agent needs this more than your backend") An MCP client that times out and retries, or a model that decides "I am not sure that worked, I will do it again", is exactly the pattern that queues the second execution, replays the same delivery twice or creates the second schedule. Your backend has a retry loop you wrote; the agent has judgment. Generate one key per **business operation** (for example, `run--`), not per attempt: it is the same key on every retry that makes the retry safe. ## Tools that accept it[​](#tools-that-accept-it "Direct link to Tools that accept it") | Family | Tools | | -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Organization and structure | `kadu_organizations_update`, `kadu_workspaces_create`, `kadu_workspaces_update`, `kadu_activities_create`, `kadu_activities_update`, `kadu_tasks_create`, `kadu_tasks_update`, `kadu_tasks_endpoint_update` | | Executions | `kadu_tasks_run`, `kadu_executions_cancel`, `kadu_executions_continue`, `kadu_todos_update` | | Memories | `kadu_memories_create`, `kadu_memories_update` | | Configuration | `kadu_environments_create`, `kadu_environments_update`, `kadu_secrets_create`, `kadu_secrets_update`, `kadu_schedules_create`, `kadu_schedules_update` | | Files and Playbook | `kadu_files_create`, `kadu_files_confirm`, `kadu_repository_sync` | | Webhooks | `kadu_webhooks_create`, `kadu_webhooks_update`, `kadu_webhooks_rotate_secret`, `kadu_webhook_deliveries_replay` | | Roles | `kadu_roles_create` | The delete tools do not accept it: deleting the same record again already answers `NOT_FOUND`, with no effect at all. :::caution The ones that run the agent `kadu_tasks_run` and `kadu_executions_continue` queue an execution, and the agent acts on the task's systems — banking, ERP, invoices — with effects no delete undoes. `kadu_webhook_deliveries_replay` delivers again to the receiver's system. Send `idempotency_key` on these even when you think you will not repeat. ::: ## Idempotency and confirmation[​](#idempotency-and-confirmation "Direct link to Idempotency and confirmation") Every call of a destructive tool asks for your [confirmation](https://docs.kadu.ia.br/en/en/mcp/overview/resources-and-prompts.md#confirming-destructive-tools) again: a client that repeats the call after a timeout produces a second question, or a second approval link. With the same `idempotency_key` on both, confirming both does not run the task twice — the second returns the first one's response. --- # Introduction The **Kadu MCP server** is a remote [Model Context Protocol](https://modelcontextprotocol.io) server: connect Claude, ChatGPT, Claude Code or an IDE and use Kadu through conversation — list tasks, run an execution, follow events and logs, resolve pending items, configure environments, secrets, schedules and webhooks, sync the Playbook and check usage and cost. It offers the operations of the [v1 API](https://docs.kadu.ia.br/en/en/api/overview/introduction.md) as **tools** the AI client calls. You connect it by URL; there is nothing to install. It is served on its own host, `mcp.kadu.ia.br`, over **Streamable HTTP** in stateless mode, authenticated by a **Bearer token** issued by the **Kobana Account**: through the **OAuth 2.1** flow, where the client gets only the URL and signs in on its own, or with a **personal token** created in the Kobana Account token wizard. No client certificate is involved. ## When to use it[​](#when-to-use-it "Direct link to When to use it") | | v1 API | MCP server (this) | | -------------- | ------------------------------- | ------------------------------------------------------ | | Consumer | your backend, an integration | an AI client (Claude, ChatGPT, Claude Code, an IDE) | | Setup | token, out of band | a URL | | Transport | HTTPS/REST | Streamable HTTP (JSON-RPC 2.0) | | Authentication | Bearer token issued for the API | OAuth 2.1 or personal token, always issued for the MCP | | Surface | the `/v1` routes | one tool per `/v1` operation | The MCP server **does not replace** the API. Each tool dispatches in-process to the route handler that implements the operation, so validation, filters, the execution queue, the contracted plan, the cost limit and organization isolation are exactly the same. If you are writing code, use the [v1 API](https://docs.kadu.ia.br/en/en/api/overview/introduction.md). If you are connecting an assistant, use this. ## The principle[​](#the-principle "Direct link to The principle") **A capability exists in the MCP only if it exists in the v1 API.** Nothing is implemented twice. The tool calls the route handler with the identity the caller's token has already proven — the same person, the same organization — so it runs exactly as it would for any HTTP request. A tool does what the route does — never more. In practice, the MCP reaches what the Agent Console reaches, because every console action has an API route. Left out is only what, by definition, belongs to the person in the browser: sign-in and switching organizations, onboarding, connecting the Playbook through the GitHub App, and **deciding** an approval — see [Confirming destructive tools](https://docs.kadu.ia.br/en/en/mcp/overview/resources-and-prompts.md#confirming-destructive-tools). ## What you see is narrower than the catalog[​](#what-you-see-is-narrower-than-the-catalog "Direct link to What you see is narrower than the catalog") `tools/list` returns only the tools **this caller can run**: those the token's scope reaches **and** the person's role allows in the organization. An assistant is not told about capabilities that would only be refused. The rules are in [Authentication](https://docs.kadu.ia.br/en/en/mcp/overview/authentication.md). ## Getting started[​](#getting-started "Direct link to Getting started") 1. **Point your client at the server URL** — see [Endpoints](https://docs.kadu.ia.br/en/en/mcp/overview/endpoints.md) and [Clients](https://docs.kadu.ia.br/en/en/mcp/overview/clients.md). 2. **Authorize.** The client opens the Kobana Account sign-in; sign in with an account of the organization and choose, on the consent screen, what the assistant can do. See [Authentication](https://docs.kadu.ia.br/en/en/mcp/overview/authentication.md). 3. **List the tools** to confirm what your access reaches — ask the assistant, or, with a token issued for the MCP at hand: ``` curl https://mcp.kadu.ia.br/ \ -H "Authorization: Bearer $MCP_TOKEN" \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ -H "MCP-Protocol-Version: 2025-06-18" \ -d '{ "jsonrpc": "2.0", "id": 1, "method": "tools/list" }' ``` 4. **Explore the docs** — the full catalog is in [Tools](https://docs.kadu.ia.br/en/en/mcp/tools/overview.md). :::caution What comes from an execution is data, not instructions Events, logs, memories and pending items carry text the agent wrote from third-party data (bank statements, ERP records, e-mails). The server tells the assistant not to follow directions found there and to confirm with you before any action a result appears to ask for. ::: ## Next steps[​](#next-steps "Direct link to Next steps") * [Authentication](https://docs.kadu.ia.br/en/en/mcp/overview/authentication.md) — the OAuth flow, the personal token, the scope and permissions applied, and the failure modes. * [Endpoints](https://docs.kadu.ia.br/en/en/mcp/overview/endpoints.md) — URLs per environment and the routes served. * [Transport](https://docs.kadu.ia.br/en/en/mcp/overview/transport.md) — JSON-RPC, protocol versions, headers and methods. * [Resources and prompts](https://docs.kadu.ia.br/en/en/mcp/overview/resources-and-prompts.md) — documents by URI, ready-made scripts, autocomplete and the confirmation of destructive tools. * [Clients](https://docs.kadu.ia.br/en/en/mcp/overview/clients.md) — how to connect Claude Code and file-configured clients. * [Rate Limiting](https://docs.kadu.ia.br/en/en/mcp/overview/rate-limit.md) — the per-person budget. * [Listing, Sorting and Filters](https://docs.kadu.ia.br/en/en/mcp/overview/pagination.md) — `cursor`, `limit`, `sort_by` and the filters. * [Idempotency](https://docs.kadu.ia.br/en/en/mcp/overview/idempotency.md) — `idempotency_key` on writes. * [Errors](https://docs.kadu.ia.br/en/en/mcp/overview/errors.md) — the two failure channels, and why the distinction matters. * [Tools](https://docs.kadu.ia.br/en/en/mcp/tools/overview.md) — the tool catalog. --- # Listing, Sorting and Filters The `*_list` tools use the same pagination as the v1 API: **by cursor** (see [Pagination, sorting and filters](https://docs.kadu.ia.br/en/en/api/overview/pagination.md)). | Argument | Default | Rule | | -------- | ------- | ------------------------------------------- | | `limit` | `25` | 1 to 100 | | `cursor` | — | the `meta.next_cursor` of the previous page | ``` { "name": "kadu_executions_list", "arguments": { "status": "failed", "limit": 50 } } ``` The result carries the usual envelope: ``` { "data": [ /* ... */ ], "meta": { "next_cursor": "eyJpZCI6IjJmMWMi…", "limit": 50 } } ``` Repeat the call with `cursor` set to `next_cursor` until it comes back `null`. The cursor is opaque: do not build or interpret it. An invalid cursor returns `VALIDATION_ERROR`. :::note Lists without a cursor Some listings return a fixed set, and the matching tool offers neither `cursor` nor `limit`: webhook deliveries (`kadu_webhook_deliveries_list`, the 50 most recent), the organization's members (`kadu_users_list`, up to 100), system events (`kadu_system_events_list`, the 100 most recent) and the execution search (`kadu_executions_search`). An execution's events, logs and pending items come whole and in order; to read only the new ones, pass `after_sequence` (`kadu_executions_events_list`) or `after_id` (`kadu_executions_logs_list`). This is intentional: a tool that announced `cursor` on a route that ignores it would make the model page forever through a list that does not move. ::: ## Sorting[​](#sorting "Direct link to Sorting") | Argument | Values | Default | | ------------ | ---------------------------------------------------------------- | ------------ | | `sort_by` | `created_at`, `updated_at` or a filterable field of the resource | `created_at` | | `sort_order` | `asc` or `desc` | `desc` | ``` { "name": "kadu_executions_list", "arguments": { "sort_by": "created_at", "sort_order": "asc" } } ``` A `sort_by` the resource does not sort by returns `VALIDATION_ERROR`, as in the API. A `sort_order` that is neither `asc` nor `desc` is refused before reaching the route, with `VALIDATION_ERROR` and the message `Invalid tool arguments.`. `kadu_operation_approvals_list` does not sort: approvals always come newest first. ## Filters[​](#filters "Direct link to Filters") Each listing tool declares the resource's filters as named arguments — its `inputSchema` says which. They become the API filters: | Argument shape | Example | Becomes | | ----------------------------- | ---------------------------------- | -------------------------------- | | One value | `status: "failed"` | `status=failed` | | A list (up to 100) | `status: ["failed", "cancelled"]` | `status[in]=failed,cancelled` | | `_gte` / `_lte` | `reference_date_gte: "2026-10-01"` | `reference_date[gte]=2026-10-01` | ``` { "name": "kadu_executions_list", "arguments": { "status": ["failed", "cancelled"], "reference_date_gte": "2026-10-01", "limit": 50 } } ``` An argument the tool does not declare is dropped before the call — the page size, for example, cannot get past 100 inside a filter. ## Data contract[​](#data-contract "Direct link to Data contract") What holds for the v1 API holds here, because it is the same code (see [Conventions](https://docs.kadu.ia.br/en/en/api/overview/conventions.md)): * **`snake_case` everywhere**, arguments and responses. * **Dates** in ISO 8601 in UTC; reference dates as `YYYY-MM-DD`. * **Cost** in dollars in the `_usd` fields; **price** in reais in the `_brl` fields. * **Secret values are never returned**, by any tool. * **Writes accept `idempotency_key`** — see [Idempotency](https://docs.kadu.ia.br/en/en/mcp/overview/idempotency.md). --- # Rate Limiting The MCP server accepts **600 requests per minute per person and organization** — the token's `sub` and `org`, not the token itself. Switching tokens does not reset the budget: a new token, from a new OAuth authorization or a new personal token, of the same person in the same organization lands in the same bucket. The same person in another organization has a budget of their own. A tool call is **never cheaper than the route it mirrors**: the request is counted once in the MCP's own bucket and, when it is a `tools/call`, again in the v1 API bucket (600 per minute), with the same person-and-organization key. Routes with a limit of their own apply it too: `kadu_executions_search` has another 30 per minute, like `GET /v1/executions/search`. This is deliberate — an agent sweeping listings in a loop must not get around the limit the same sweep would pay through the API. Attempts with a refused token are throttled **per IP**, not per token: a token that does not verify cannot have a bucket of its own. A request with no token at all — every client's first step, before OAuth — does not count as an attempt. ## When the limit is reached[​](#when-the-limit-is-reached "Direct link to When the limit is reached") When the MCP bucket runs out, the server answers `429` — **the JSON-RPC message is never processed**: ``` HTTP/1.1 429 Too Many Requests Retry-After: 37 X-RateLimit-Limit: 600 X-RateLimit-Remaining: 0 X-RateLimit-Reset: 1793280037 Cache-Control: no-store Content-Type: application/json { "error": { "code": "TOO_MANY_REQUESTS", "message": "Rate limit exceeded." } } ``` Wait the `Retry-After` seconds before the next request. `X-RateLimit-Limit` and `X-RateLimit-Remaining` give the bucket size and what is left; `X-RateLimit-Reset` is when it renews, in seconds since the Unix epoch. When the route bucket runs out, the HTTP request is a `200` and the tool result comes back `isError` with the API error, `RATE_LIMITED` — see [Errors](https://docs.kadu.ia.br/en/en/mcp/overview/errors.md). Wait a few seconds before calling again. The API limits are in [Rate limits](https://docs.kadu.ia.br/en/en/api/overview/rate-limit.md). ## Spending less[​](#spending-less "Direct link to Spending less") An agent spends budget like your backend does, and also pays context for every row it brings back. Three habits solve almost everything: * **Filter before paginating.** `kadu_executions_list { status: "failed", started_at_gte: "2026-10-01" }` costs one request; sweeping every execution to filter in the model costs dozens. * **Read only what is new.** To follow a running execution, pass `after_sequence` to `kadu_executions_events_list` and `after_id` to `kadu_executions_logs_list`, instead of re-reading everything on each round. * **Do not poll in a loop.** An approval by link is checked when the person says they decided, not every few seconds. --- # Resources and prompts Besides tools, the server offers three things the **client** uses on its own: **resources** (documents the user attaches to the conversation), **prompts** (ready-made scripts the user picks) and **completions** (autocomplete for the arguments of both). And before running a destructive tool, it asks for your **confirmation**. None of this opens a new path to your data: everything goes through the same tools, under the same [token scope](https://docs.kadu.ia.br/en/en/mcp/overview/authentication.md#token-scope) and the same person's role. A resource or prompt that relies on a tool you cannot run simply does not appear. ## Resources[​](#resources "Direct link to Resources") A resource is a document identified by a URI. The user picks, say, an execution, and the client reads the document and puts it in context, without the model having to call a tool. All of them come back as JSON. | URI | What comes back | Tool that reads it | | ---------------------------------------- | --------------------------------------------------------- | ------------------------------- | | `kobana://kadu/me` | The person, organization and scopes of the connection | `kadu_me_get` | | `kobana://kadu/repository` | The organization's Kadu Playbook and its skill catalog | `kadu_repository_get` | | `kobana://kadu/operation_approvals` | Your approvals by link, newest first | `kadu_operation_approvals_list` | | `kobana://kadu/operation_approvals/{id}` | One approval: what was asked, the decision and the result | `kadu_operation_approvals_get` | | `kobana://kadu/workspaces/{id}` | A workspace | `kadu_workspaces_get` | | `kobana://kadu/activities/{id}` | An activity | `kadu_activities_get` | | `kobana://kadu/tasks/{id}` | A task and its parameters schema | `kadu_tasks_get` | | `kobana://kadu/executions/{id}` | An execution | `kadu_executions_get` | | `kobana://kadu/executions/{id}/events` | The events the agent reported during the execution | `kadu_executions_events_list` | | `kobana://kadu/executions/{id}/logs` | The execution logs | `kadu_executions_logs_list` | | `kobana://kadu/executions/{id}/todos` | The pending items the execution opened | `kadu_executions_todos_list` | | `kobana://kadu/memories/{id}` | A memory of the agent | `kadu_memories_get` | | `kobana://kadu/environments/{id}` | An environment | `kadu_environments_get` | | `kobana://kadu/schedules/{id}` | A schedule | `kadu_schedules_get` | `resources/list` returns the fixed URIs and `resources/templates/list` the templates with `{id}`. * **Permissions.** Each resource is read by the tool in the last column. It only appears, and is only read, if you can run that tool; otherwise the read returns `-32602` with `data.code: "insufficient_permission"`. * **No binaries.** File content never comes as a resource: it is downloaded through the presigned URL of `kadu_files_download`. * **Not found.** An unknown URI, or a record that does not exist in this organization, is `-32002` in the 2025 era and `-32602` in `2026-07-28` — see [Errors](https://docs.kadu.ia.br/en/en/mcp/overview/errors.md). * **No change subscriptions.** The server is stateless: there is no `resources/subscribe` and no list-changed notice. ## Prompts[​](#prompts "Direct link to Prompts") A prompt is a ready-made instruction you pick in the client that becomes the first message of the conversation. In Kadu, **every prompt only reads**: running, cancelling or changing configuration is always something you ask for explicitly. A prompt appears only if you can run **all** the tools it relies on. | Prompt | Arguments | What it does | | -------------------------- | --------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `review_failed_executions` | `since` (optional, `YYYY-MM-DD`; default: seven days ago) | Lists the executions that failed since the date and explains what went wrong in each one, grouping repeated causes, without running, cancelling or continuing anything. | | `explain_execution` | `execution_id` | Explains, step by step, what the agent did in one execution: the systems it touched, the result and the pending items left for a person. | | `monthly_usage_report` | `month` (`YYYY-MM`) | Summarizes usage and cost of the month's executions, by task and by workspace, and points out the tasks that cost the most. | | `check_task_readiness` | `task_id`, `environment_id` | Says whether the task can run now in that environment and, if not, exactly what is missing (plan, cost limit, secrets, repository), without running anything. | The script arrives with the dates already worked out. A missing required argument, or `since` and `month` in the wrong format, is `-32602`. ## Completions[​](#completions "Direct link to Completions") `completion/complete` suggests values while you type an argument: * **Ids** (`execution_id`, `task_id`, `environment_id` and the `{id}` of the templates): the recent ids of the matching listing, filtered by what you have typed, under that listing's permission. If you cannot list, there is no suggestion. * **`month`**: the last twelve months. At most 100 values. With no source of suggestions, the answer is an empty list, and you keep typing. ## Confirming destructive tools[​](#confirming-destructive-tools "Direct link to Confirming destructive tools") Destructive tools are marked with `destructiveHint`: * every delete tool (`*_delete`, including disabling a task's trigger endpoint); * `kadu_tasks_run` and `kadu_executions_continue`, which run the financial agent — and it acts on banking, the ERP and invoices; * `kadu_executions_cancel`; * `kadu_webhook_deliveries_replay` and `kadu_webhooks_rotate_secret`; * `kadu_tasks_endpoint_update`, which changes who can run the agent; * `kadu_webhooks_create`, and `kadu_webhooks_update` when it changes the `url` — where the organization's data goes. The **server** does not rely on the client alone: it enforces the confirmation itself, in one of two ways, depending on the client. ### Confirmation in the chat (era `2026-07-28`)[​](#confirmation-in-the-chat-era-2026-07-28 "Direct link to confirmation-in-the-chat-era-2026-07-28") When the client speaks era `2026-07-28` and declares support for elicitation forms: 1. The first `tools/call` runs nothing. It returns `resultType: "input_required"` with a one-question form: run this action, with these arguments? 2. The client shows the question and re-sends the same call with your answer. 3. Only an explicit **yes** runs it. Declining or cancelling returns an `isError` result with `USER_DECLINED`, and nothing changes. The confirmation is valid **only for that call**: the same tool, the same arguments, the same person and organization, for **5 minutes**. An answer cannot be reused for another task or another action; if anything does not match, the server simply asks again. And it is **single use**: re-sending a call already confirmed returns `isError` with `confirmation_replayed`, running nothing — to repeat the action, call the tool again and confirm again. ### Client without the confirmation form: approval by link[​](#client-without-the-confirmation-form-approval-by-link "Direct link to Client without the confirmation form: approval by link") A client that **cannot confirm in the chat** — in the 2025 era, or on `2026-07-28` without declaring elicitation forms — can still use destructive tools, through an **approval link**. The call **runs nothing**: the server stores the operation as pending and answers successfully (no `isError`), in a single JSON text: ``` { "status": "approval_required", "approval": { "id": "6f0c2d1e-…", "status": "pending", "url": "https://app.kadu.ia.br/approvals/…", "expires_at": "2026-10-31T12:15:00.000Z", "resource_uri": "kobana://kadu/operation_approvals/6f0c2d1e-…", "check_with": { "tool": "kadu_operation_approvals_get", "arguments": { "id": "6f0c2d1e-…" } } }, "message": "… Show this URL to the user … Do not call the tool again …" } ``` 1. The assistant shows the URL. Do not call the tool again: that would create another approval. 2. You open the link **signed in to Kadu** (without a session, it goes through sign-in and comes back to the link). The screen shows what will happen ("Run the task", "Delete the environment"), who asked, when, through which client, the current data of what the operation acts on and **every** argument that will run. 3. **Continue** runs the operation right away, with the permissions you have **now**: if you lost the permission or your membership in the organization since the request, or if the token that asked was revoked, the run fails and nothing changes. **Cancel** ends it without running anything. The link is valid for **15 minutes** and decides **only once**: two clicks on Continue run once, and an expired or cancelled link does not run. Only **you** — the person behind the token that made the call — can open the link: for anyone else, even in your organization, it does not exist. If what the operation acts on changes between opening the screen and clicking Continue (a webhook that changed its URL, a task that was edited), nothing runs (`subject_changed`): ask for the operation again and review the new link. Your approvals are listed in **Settings › Approvals** in the console, with tabs by status (Pending, Executed, Cancelled, Failed, Expired); clicking a row opens the approval read-only — a pending one is decided through the link the assistant showed. #### Following the approval[​](#following-the-approval "Direct link to Following the approval") The assistant itself follows the approval it asked for: with the `id` from the answer, `kadu_operation_approvals_get` (or the `kobana://kadu/operation_approvals/{id}` resource) shows the status — `pending`, then `executed`, `failed`, `declined` or `expired` — and what the operation returned. `check_with` is included in the answer only when the caller can run that read (scope `kadu/system_events` and permission `agent.operation_approvals.read`). The assistant should check when you say you decided, not in a loop. Reading **never decides**: there is no tool and no API route that continues or cancels an approval. If there were, whoever asked for the operation could also approve it. A secret in the result — the new signing secret from `kadu_webhooks_rotate_secret` — is shown in clear **once**, on the screen, right after Continue; the read tool and the reloaded screen show it masked. #### When confirmation is not possible[​](#when-confirmation-is-not-possible "Direct link to When confirmation is not possible") A token with no person of the organization who could open the link returns `isError` with `confirmation_required`. If the server cannot record the confirmation or the approval at that moment, the tool is refused with `confirmation_unavailable`; try again later. In both cases nothing runs, and the other tools keep working normally. --- # Transport **Streamable HTTP, stateless.** One JSON-RPC 2.0 message per `POST`, one JSON response. There is no `Mcp-Session-Id`, no resumability and no server→client stream: the application runs several replicas, and every long operation answers right away and finishes in the background, not on the MCP channel. Running a task (`kadu_tasks_run`), for example, queues the execution and answers at once; you follow it with `kadu_executions_get`. ## Protocol versions[​](#protocol-versions "Direct link to Protocol versions") The server speaks two protocol eras on the same endpoint: | Version | Era | Status | | ------------ | ------------------------------------------------------------- | --------- | | `2026-07-28` | Stateless, no handshake — discovery through `server/discover` | Supported | | `2025-06-18` | Handshake through `initialize` | Supported | | `2025-03-26` | Handshake through `initialize` | Supported | The era is decided **by the request body**, with the headers as a cross-check: * **`params._meta` carries `io.modelcontextprotocol/protocolVersion`** → era `2026-07-28` (see below). * **Without that envelope**, with a 2025 `MCP-Protocol-Version` or no header → the 2025 era, with `initialize`. * **`MCP-Protocol-Version: 2026-07-28` without the envelope** → `400` with `-32602`. The server does not silently fall back to the old era: you asked for stateless semantics and would get the other ones. A version the server does not speak — in the header or in the envelope — gets `400` and the JSON-RPC error `-32022`, listing the supported ones: ``` { "jsonrpc": "2.0", "id": 1, "error": { "code": -32022, "message": "Unsupported protocol version", "data": { "supported": ["2026-07-28", "2025-06-18", "2025-03-26"], "requested": "1999-01-01" } } } ``` The version in use comes back in the response's `MCP-Protocol-Version` header. An `initialize` asking for a version outside the 2025 era gets `2025-06-18`; `2026-07-28` is discovered through `server/discover`. ### Era `2026-07-28`[​](#era-2026-07-28 "Direct link to era-2026-07-28") There is no handshake: **every** request carries its own version and the client capabilities in `params._meta`, and the server remembers nothing between one request and the next. | `params._meta` key | Required | Meaning | | -------------------------------------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------- | | `io.modelcontextprotocol/protocolVersion` | yes | `2026-07-28` | | `io.modelcontextprotocol/clientCapabilities` | yes | Client capabilities (an object; `{}` will do). Declaring `elicitation` is what lets a destructive tool be confirmed in the chat. | | `io.modelcontextprotocol/clientInfo` | recommended | Client `name` and `version`. The name shows on the approval-by-link screen. | The headers repeat what is in the body, so intermediaries can route without reading it — and so they have to agree with it: | Header | Must equal | | ---------------------- | ------------------------------------------------------------- | | `MCP-Protocol-Version` | The envelope version | | `Mcp-Method` | The message `method` | | `Mcp-Name` | Only on `tools/call`: `params.name`, raw or as `=?base64?…?=` | Any mismatch is `400` with `-32020` (`Header mismatch: …`). A missing `clientCapabilities` is `400` with `-32602`. Results in this era always carry `resultType` and the server in `_meta["io.modelcontextprotocol/serverInfo"]`. `server/discover`, `tools/list`, `resources/list`, `resources/templates/list`, `resources/read` and `prompts/list` also carry `ttlMs: 0` and `cacheScope: "private"`: what comes back is filtered by the caller's permissions, so it must not be reused or shared. A `tools/call` of a destructive tool may return `resultType: "input_required"` — see [Confirming destructive tools](https://docs.kadu.ia.br/en/en/mcp/overview/resources-and-prompts.md#confirming-destructive-tools). ## Request headers[​](#request-headers "Direct link to Request headers") | Header | Required | Meaning | | ---------------------- | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | | `Authorization` | yes | `Bearer ` issued for the MCP — see [Authentication](https://docs.kadu.ia.br/en/en/mcp/overview/authentication.md) | | `Content-Type` | yes | `application/json` | | `Accept` | recommended | `application/json` | | `MCP-Protocol-Version` | yes in era `2026-07-28`; recommended in 2025 | Protocol revision | | `Mcp-Method` | yes in era `2026-07-28` | The message method | | `Mcp-Name` | yes on `tools/call` in era `2026-07-28` | The tool name | | `Origin` | conditional | Sent by browser clients; only the server's own or `localhost` is accepted | There is no organization-selection header: it comes from the token itself, and no tool takes an organization argument. The request body is limited to **1 MB**, the same as the API. Above that — by the declared `Content-Length` or by what actually arrives — the answer is `413` with `{ "jsonrpc": "2.0", "id": null, "error": { "code": -32600, "message": "Request body too large (limit 1048576 bytes)" } }`. No tool receives file content (see [Files](#files)), so a legitimate call stays far below that. ## Response headers[​](#response-headers "Direct link to Response headers") | Header | Meaning | | ---------------------- | -------------------------------------------------------------------------------------------------- | | `MCP-Protocol-Version` | Revision in use | | `Cache-Control` | Always `no-store` | | `Retry-After` | On `429` and `503` — see [Rate Limiting](https://docs.kadu.ia.br/en/en/mcp/overview/rate-limit.md) | ## Methods[​](#methods "Direct link to Methods") | Method | Era `2026-07-28` | Era 2025 | What for | | -------------------------- | ---------------- | -------- | ---------------------------------------------------------------------- | | `server/discover` | yes | — | Supported versions, server capabilities and instructions | | `initialize` | — | yes | Version negotiation, server capabilities and instructions | | `ping` | — | yes | Liveness check; answers an empty result | | `tools/list` | yes | yes | The tools this caller can run, with JSON Schema and annotations | | `tools/call` | yes | yes | Runs a tool | | `resources/list` | yes | yes | The fixed-URI documents this caller can read | | `resources/templates/list` | yes | yes | The URI templates (`kobana://kadu/tasks/{id}`, …) this caller can read | | `resources/read` | yes | yes | Reads a document by URI | | `prompts/list` | yes | yes | The ready-made scripts this caller can use | | `prompts/get` | yes | yes | Builds a script with the given arguments | | `completion/complete` | yes | yes | Suggestions for a prompt or template argument | Resources, prompts and completions are in [Resources and prompts](https://docs.kadu.ia.br/en/en/mcp/overview/resources-and-prompts.md). Any other method is `-32601 Method not found` — in era `2026-07-28`, with HTTP `404`; in 2025, in the body of a `200`. **JSON-RPC batching is not supported** — it was removed in revision `2025-06-18`, and an array body gets `400`. A **notification** (a message without `id`, such as `notifications/initialized`) is accepted with `202` and an empty body, in both eras. Authentication comes **before** any method, `server/discover` included: without a token the answer is the `401` with the challenge that takes the client to the OAuth sign-in — see [Authentication](https://docs.kadu.ia.br/en/en/mcp/overview/authentication.md). ### `server/discover` (era `2026-07-28`)[​](#serverdiscover-era-2026-07-28 "Direct link to serverdiscover-era-2026-07-28") ``` POST / HTTP/1.1 Authorization: Bearer Content-Type: application/json MCP-Protocol-Version: 2026-07-28 Mcp-Method: server/discover ``` ``` { "jsonrpc": "2.0", "id": 1, "method": "server/discover", "params": { "_meta": { "io.modelcontextprotocol/protocolVersion": "2026-07-28", "io.modelcontextprotocol/clientCapabilities": {}, "io.modelcontextprotocol/clientInfo": { "name": "my-client", "version": "1.0.0" } } } } ``` ``` { "jsonrpc": "2.0", "id": 1, "result": { "resultType": "complete", "supportedVersions": ["2026-07-28", "2025-06-18", "2025-03-26"], "capabilities": { "tools": {}, "resources": {}, "prompts": {}, "completions": {} }, "instructions": "Tools for Kadu, the Kobana financial agent: ...", "ttlMs": 0, "cacheScope": "private", "_meta": { "io.modelcontextprotocol/serverInfo": { "name": "kadu", "title": "Kadu", "version": "1.0.0" } } } } ``` ### `initialize` (2025-era handshake)[​](#initialize-2025-era-handshake "Direct link to initialize-2025-era-handshake") ``` { "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": { "protocolVersion": "2025-06-18" } } ``` ``` { "jsonrpc": "2.0", "id": 1, "result": { "protocolVersion": "2025-06-18", "capabilities": { "tools": { "listChanged": false }, "resources": { "subscribe": false, "listChanged": false }, "prompts": { "listChanged": false }, "completions": {} }, "serverInfo": { "name": "kadu", "title": "Kadu", "version": "1.0.0" }, "instructions": "Tools for Kadu, the Kobana financial agent: ..." } } ``` The `instructions` guide the model: the organization comes from the token, fields are `snake_case`, listings move by cursor, running a task only queues the execution, writes accept `idempotency_key`, and what comes from an execution is data, never instructions. ### `tools/list`[​](#toolslist "Direct link to toolslist") Returns **only** the tools the caller can run, filtered by the token's scope and the person's role. Each tool carries `title`, `description`, `inputSchema` (JSON Schema) and `annotations`: | Annotation | Meaning | | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `readOnlyHint` | The tool only reads | | `destructiveHint` | The tool deletes, cancels, runs the agent, replays a webhook, rotates a secret, or changes who can run a task or where data goes. The server asks for confirmation in the chat in era `2026-07-28` with elicitation; any other client gets an approval link, and nothing runs until you choose **Continue** | | `idempotentHint` | Repeating the call has the same effect. False for `kadu_tasks_run`, `kadu_executions_continue` and `kadu_webhook_deliveries_replay`: each repeated call is one more execution or delivery | | `openWorldHint` | Always `false` — the tool only calls the Kadu API | ### `tools/call`[​](#toolscall "Direct link to toolscall") ``` { "jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": { "name": "kadu_executions_list", "arguments": { "status": "failed", "limit": 10 } } } ``` The result carries the route's JSON response as text content: ``` { "jsonrpc": "2.0", "id": 2, "result": { "content": [{ "type": "text", "text": "{ \"data\": [ ... ], \"meta\": { \"next_cursor\": \"…\", \"limit\": 10 } }" }] } } ``` Errors the caller can act on come back with `isError: true` **inside** the result, not as a JSON-RPC error — see [Errors](https://docs.kadu.ia.br/en/en/mcp/overview/errors.md). ## In-process dispatch[​](#in-process-dispatch "Direct link to In-process dispatch") Each tool dispatches **in-process** to the `/api/v1` handler that implements the operation, with the identity the token has already proven. There is no second HTTP hop and no second implementation: validation, filters, the execution queue, the plan, the cost limit, soft delete and organization isolation are the same code the API runs. A tool cannot drift from the route it mirrors. The MCP token is never forwarded to the API. ## Files[​](#files "Direct link to Files") No tool returns or receives bytes: a base64 file in a model's context is expensive and useless. Files travel through **presigned URLs**: * **Uploading an input file:** `kadu_files_create` answers with an upload URL; `PUT` the content to it and call `kadu_files_confirm`. Then pass the id in `input_file_ids` of `kadu_tasks_run`. * **Downloading a file:** `kadu_files_download` answers with a short-lived download URL. --- # Approvals and events The link approvals you gave (or not) to actions an assistant asked for — read-only: deciding always happens in the console — and the system events of the organization. ## Families on this page[​](#families-on-this-page "Direct link to Families on this page") | Family | Tools | read | write | destructive | | --------------------------------- | ----- | ---- | ----- | ----------- | | `operation_approvals` — Approvals | 2 | 2 | 0 | 0 | | `system_events` — System events | 1 | 1 | 0 | 0 | ## `operation_approvals`[​](#operation_approvals "Direct link to operation_approvals") | Tool | Kind | Scope | Permission | Endpoint | What it does | | ------------------------------- | ---- | ----------------------------- | -------------------------------- | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | `kadu_operation_approvals_list` | read | `kadu/system_events` + `read` | `agent.operation_approvals.list` | `GET /v1/operation_approvals` | Lists the approvals of the person behind the token: destructive operations an assistant asked for that wait (or waited) for their confirmation by link. | | `kadu_operation_approvals_get` | read | `kadu/system_events` + `read` | `agent.operation_approvals.read` | `GET /v1/operation_approvals/{id}` | Gets one approval of the person behind the token: what was asked, the records it acts on, the arguments, the decision and the result (secrets masked). | ## `system_events`[​](#system_events "Direct link to system_events") | Tool | Kind | Scope | Permission | Endpoint | What it does | | ------------------------- | ---- | ----------------------------- | -------------------------- | ----------------------- | ------------------------------------------------------------------------------ | | `kadu_system_events_list` | read | `kadu/system_events` + `read` | `agent.system_events.list` | `GET /v1/system_events` | Lists the 100 most recent system events (the audit trail behind the webhooks). | *** The fields of each resource are in the [API reference](https://docs.kadu.ia.br/en/en/api/v1). A tool you cannot run is not listed by `tools/list` at all. If one from this page is missing, see [Authentication](https://docs.kadu.ia.br/en/en/mcp/overview/authentication.md). --- # Configuration and files Environments and their secrets, task schedules, and the input and output files of executions (through presigned URLs — no tool carries bytes). ## Families on this page[​](#families-on-this-page "Direct link to Families on this page") | Family | Tools | read | write | destructive | | ----------------------------- | ----- | ---- | ----- | ----------- | | `environments` — Environments | 5 | 2 | 2 | 1 | | `secrets` — Secrets | 5 | 2 | 2 | 1 | | `schedules` — Schedules | 5 | 2 | 2 | 1 | | `files` — Files | 5 | 3 | 2 | 0 | ## `environments`[​](#environments "Direct link to environments") | Tool | Kind | Scope | Permission | Endpoint | What it does | | -------------------------- | ----------- | ------------------------------ | --------------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------- | | `kadu_environments_list` | read | `kadu/environments` + `read` | `agent.environments.list` | `GET /v1/environments` | Lists the environments a task can run in (each one maps to a branch of the repository and its secrets). | | `kadu_environments_get` | read | `kadu/environments` + `read` | `agent.environments.read` | `GET /v1/environments/{id}` | Gets an environment. | | `kadu_environments_create` | write | `kadu/environments` + `write` | `agent.environments.create` | `POST /v1/environments` | Creates an environment. | | `kadu_environments_update` | write | `kadu/environments` + `write` | `agent.environments.update` | `PATCH /v1/environments/{id}` | Updates an environment. | | `kadu_environments_delete` | destructive | `kadu/environments` + `delete` | `agent.environments.delete` | `DELETE /v1/environments/{id}` | Deletes an environment. | ## `secrets`[​](#secrets "Direct link to secrets") | Tool | Kind | Scope | Permission | Endpoint | What it does | | --------------------- | ----------- | ------------------------- | ---------------------- | ------------------------- | --------------------------------------------------------------- | | `kadu_secrets_list` | read | `kadu/secrets` + `read` | `agent.secrets.list` | `GET /v1/secrets` | Lists the secrets of the environments. | | `kadu_secrets_get` | read | `kadu/secrets` + `read` | `agent.secrets.read` | `GET /v1/secrets/{id}` | Gets a secret (without its value). | | `kadu_secrets_create` | write | `kadu/secrets` + `write` | `agent.secrets.create` | `POST /v1/secrets` | Creates a secret the agent receives as an environment variable. | | `kadu_secrets_update` | write | `kadu/secrets` + `write` | `agent.secrets.update` | `PATCH /v1/secrets/{id}` | Updates a secret. | | `kadu_secrets_delete` | destructive | `kadu/secrets` + `delete` | `agent.secrets.delete` | `DELETE /v1/secrets/{id}` | Deletes a secret. | ## `schedules`[​](#schedules "Direct link to schedules") | Tool | Kind | Scope | Permission | Endpoint | What it does | | ----------------------- | ----------- | --------------------------- | ------------------------ | --------------------------- | ---------------------------------------------------- | | `kadu_schedules_list` | read | `kadu/schedules` + `read` | `agent.schedules.list` | `GET /v1/schedules` | Lists the schedules that run tasks on a cron. | | `kadu_schedules_get` | read | `kadu/schedules` + `read` | `agent.schedules.read` | `GET /v1/schedules/{id}` | Gets a schedule. | | `kadu_schedules_create` | write | `kadu/schedules` + `write` | `agent.schedules.create` | `POST /v1/schedules` | Schedules a task to run on a cron in an environment. | | `kadu_schedules_update` | write | `kadu/schedules` + `write` | `agent.schedules.update` | `PATCH /v1/schedules/{id}` | Changes, pauses or resumes a schedule. | | `kadu_schedules_delete` | destructive | `kadu/schedules` + `delete` | `agent.schedules.delete` | `DELETE /v1/schedules/{id}` | Cancels a schedule. | ## `files`[​](#files "Direct link to files") | Tool | Kind | Scope | Permission | Endpoint | What it does | | --------------------- | ----- | ---------------------- | -------------------- | ----------------------------- | ----------------------------------------------------------------------------------- | | `kadu_files_list` | read | `kadu/files` + `read` | `agent.files.list` | `GET /v1/files` | Lists the input and output files of tasks and executions. | | `kadu_files_get` | read | `kadu/files` + `read` | `agent.files.read` | `GET /v1/files/{id}` | Gets a file record. | | `kadu_files_create` | write | `kadu/files` + `write` | `agent.files.create` | `POST /v1/files` | Starts the upload of an input file for a task: answers with a presigned upload URL. | | `kadu_files_confirm` | write | `kadu/files` + `write` | `agent.files.create` | `POST /v1/files/{id}/confirm` | Confirms an upload started by `kadu_files_create` (pending → uploaded). | | `kadu_files_download` | read | `kadu/files` + `read` | `agent.files.read` | `GET /v1/files/{id}/download` | Gets a short-lived presigned URL to download a file. | *** The fields of each resource are in the [API reference](https://docs.kadu.ia.br/en/en/api/v1). A tool you cannot run is not listed by `tools/list` at all. If one from this page is missing, see [Authentication](https://docs.kadu.ia.br/en/en/mcp/overview/authentication.md). --- # Executions The runs of the agent — status, events, logs, cancellation and the next turn of a conversation —, the todos they open and the memories the agent keeps between runs. ## Families on this page[​](#families-on-this-page "Direct link to Families on this page") | Family | Tools | read | write | destructive | | ------------------------- | ----- | ---- | ----- | ----------- | | `executions` — Executions | 8 | 6 | 0 | 2 | | `todos` — Todos | 1 | 0 | 1 | 0 | | `memories` — Memories | 5 | 2 | 2 | 1 | ## `executions`[​](#executions-1 "Direct link to executions-1") | Tool | Kind | Scope | Permission | Endpoint | What it does | | ----------------------------- | ----------- | --------------------------- | ------------------------- | ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | | `kadu_executions_list` | read | `kadu/executions` + `read` | `agent.executions.list` | `GET /v1/executions` | Lists the executions (runs of the agent), newest first. | | `kadu_executions_get` | read | `kadu/executions` + `read` | `agent.executions.read` | `GET /v1/executions/{id}` | Gets an execution: status, timing, cost, result summary and its parent/root when it is a conversation turn. | | `kadu_executions_search` | read | `kadu/executions` + `read` | `agent.executions.list` | `GET /v1/executions/search` | Full-text search across the events, todos and logs of the executions. | | `kadu_executions_cancel` | destructive | `kadu/executions` + `write` | `agent.executions.update` | `POST /v1/executions/{id}/cancel` | Cancels a queued, pending or running execution. | | `kadu_executions_continue` | destructive | `kadu/executions` + `write` | `agent.executions.run` | `POST /v1/executions/{id}/continue` | Continues the conversation of a finished execution of a conversational task: queues the next turn of the agent with your message. | | `kadu_executions_events_list` | read | `kadu/executions` + `read` | `agent.events.list` | `GET /v1/executions/{id}/events` | Lists the events the agent reported during an execution, in order. | | `kadu_executions_logs_list` | read | `kadu/executions` + `read` | `agent.logs.list` | `GET /v1/executions/{id}/logs` | Lists the logs of an execution, in order. | | `kadu_executions_todos_list` | read | `kadu/executions` + `read` | `agent.todos.list` | `GET /v1/executions/{id}/todos` | Lists the todos (pending items for a person) an execution opened. | ## `todos`[​](#todos "Direct link to todos") | Tool | Kind | Scope | Permission | Endpoint | What it does | | ------------------- | ----- | ---------------------- | -------------------- | ---------------------- | --------------------------------------- | | `kadu_todos_update` | write | `kadu/todos` + `write` | `agent.todos.update` | `PATCH /v1/todos/{id}` | Resolves a todo opened by an execution. | ## `memories`[​](#memories "Direct link to memories") | Tool | Kind | Scope | Permission | Endpoint | What it does | | ---------------------- | ----------- | -------------------------- | ----------------------- | -------------------------- | ------------------------------------------------------ | | `kadu_memories_list` | read | `kadu/memories` + `read` | `agent.memories.list` | `GET /v1/memories` | Lists the memories the agent keeps between executions. | | `kadu_memories_get` | read | `kadu/memories` + `read` | `agent.memories.read` | `GET /v1/memories/{id}` | Gets a memory. | | `kadu_memories_create` | write | `kadu/memories` + `write` | `agent.memories.create` | `POST /v1/memories` | Creates a memory for the agent. | | `kadu_memories_update` | write | `kadu/memories` + `write` | `agent.memories.update` | `PATCH /v1/memories/{id}` | Updates a memory. | | `kadu_memories_delete` | destructive | `kadu/memories` + `delete` | `agent.memories.delete` | `DELETE /v1/memories/{id}` | Deletes a memory. | *** The fields of each resource are in the [API reference](https://docs.kadu.ia.br/en/en/api/v1). A tool you cannot run is not listed by `tools/list` at all. If one from this page is missing, see [Authentication](https://docs.kadu.ia.br/en/en/mcp/overview/authentication.md). --- # Organization and team Who the connection is, the token's organization and the people who use the console, with their memberships and roles. ## Families on this page[​](#families-on-this-page "Direct link to Families on this page") | Family | Tools | read | write | destructive | | ------------------------------ | ----- | ---- | ----- | ----------- | | `me` — Connection identity | 1 | 1 | 0 | 0 | | `organizations` — Organization | 6 | 3 | 2 | 1 | | `users` — Users | 1 | 1 | 0 | 0 | | `memberships` — Memberships | 1 | 1 | 0 | 0 | | `roles` — Roles | 2 | 1 | 1 | 0 | ## `me`[​](#me "Direct link to me") | Tool | Kind | Scope | Permission | Endpoint | What it does | | ------------- | ---- | ------------- | ------------------- | ------------ | --------------------------------------------------------------------------------------------------------------- | | `kadu_me_get` | read | — (any token) | — (membership only) | `GET /v1/me` | Who this connection is: the person (`sub`), the organization the token acts for, and the scopes it was granted. | ## `organizations`[​](#organizations "Direct link to organizations") | Tool | Kind | Scope | Permission | Endpoint | What it does | | ----------------------------------------------- | ----------- | ------------------------------- | ---------------------------- | -------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `kadu_organizations_list` | read | `kadu/organizations` + `read` | `agent.organizations.list` | `GET /v1/organizations` | Lists the organization the token acts for (a token always reaches exactly one). | | `kadu_organizations_get` | read | `kadu/organizations` + `read` | `agent.organizations.read` | `GET /v1/organizations/{id}` | Gets the organization: label, timezone, monthly cost limit and the cost accumulated this month (USD). | | `kadu_organizations_update` | write | `kadu/organizations` + `write` | `agent.organizations.update` | `PATCH /v1/organizations/{id}` | Updates the token's own organization: label, timezone, custom metadata. | | `kadu_organizations_slack_installations_list` | read | `kadu/organizations` + `read` | `agent.organizations.read` | `GET /v1/organizations/{id}/slack_installations` | Lists the organization's Slack bot installations: workspace, where the bot answers, output files mode, detail in the thread and connected accounts. | | `kadu_organizations_slack_installations_update` | write | `kadu/organizations` + `write` | `agent.organizations.update` | `PATCH /v1/organizations/{id}/slack_installations/{id}` | Configures a Slack installation: allowed channel types (public channels and Slack Connect expose results to more people), output files as console link or attachment, and log detail in the thread. | | `kadu_organizations_slack_installations_delete` | destructive | `kadu/organizations` + `delete` | `agent.organizations.update` | `DELETE /v1/organizations/{id}/slack_installations/{id}` | Removes a Slack installation: revokes the bot token in Slack, the bot stops answering and account connections are undone. | ## `users`[​](#users "Direct link to users") | Tool | Kind | Scope | Permission | Endpoint | What it does | | ----------------- | ---- | --------------------- | ------------------ | --------------- | -------------------------------------------------- | | `kadu_users_list` | read | `kadu/users` + `read` | `agent.users.list` | `GET /v1/users` | Lists the members of the organization (up to 100). | ## `memberships`[​](#memberships "Direct link to memberships") | Tool | Kind | Scope | Permission | Endpoint | What it does | | ----------------------- | ---- | --------------------------- | ------------------------ | --------------------- | ----------------------------------------------------------------------- | | `kadu_memberships_list` | read | `kadu/memberships` + `read` | `agent.memberships.list` | `GET /v1/memberships` | Lists the memberships: which user holds which role in the organization. | ## `roles`[​](#roles "Direct link to roles") | Tool | Kind | Scope | Permission | Endpoint | What it does | | ------------------- | ----- | ---------------------- | -------------------- | ---------------- | ---------------------------------------------------------------------------------- | | `kadu_roles_list` | read | `kadu/roles` + `read` | `agent.roles.list` | `GET /v1/roles` | Lists the roles and the permissions each one grants (`agent..`). | | `kadu_roles_create` | write | `kadu/roles` + `write` | `agent.roles.create` | `POST /v1/roles` | Creates a role. | *** The fields of each resource are in the [API reference](https://docs.kadu.ia.br/en/en/api/v1). A tool you cannot run is not listed by `tools/list` at all. If one from this page is missing, see [Authentication](https://docs.kadu.ia.br/en/en/mcp/overview/authentication.md). --- # Tools The server exposes **80 tools** over **21 resource families** — one tool per operation of the [v1 API](https://docs.kadu.ia.br/en/en/api/overview/introduction.md). Each one dispatches in-process to the `/v1` endpoint that implements the operation, so validation, filters, scope, permission and tenant isolation are exactly the REST API's: the tool *is* the endpoint. ## How to read these pages[​](#how-to-read-these-pages "Direct link to How to read these pages") Every tool carries five facts: > `kadu_tasks_run` · **destructive** · scope `kadu/tasks` + `write` · permission `agent.executions.run` · `POST /v1/tasks/{id}/run` | Field | Meaning | | -------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **read** / **write** / **destructive** | The annotation the MCP client reads. A `destructive` tool (deletes, cancels, runs the agent, replays a webhook, rotates a secret, changes who may trigger a task) runs only after you confirm — in the client's form or through an approval link ([Confirming destructive tools](https://docs.kadu.ia.br/en/en/mcp/overview/resources-and-prompts.md#confirming-destructive-tools)). **destructive (depending on arguments)** asks for confirmation only when the arguments make the call destructive (a webhook changing its URL). | | **scope** | The resource (`kadu/`) and the verb the token must carry: `read`, `write` or `delete` (the `*_delete` tools). `write` does not delete. See [Authentication](https://docs.kadu.ia.br/en/en/mcp/overview/authentication.md). | | **permission** | The console permission the role of the token's person must hold in the organization — the same one the v1 API checks ([Permissions](https://docs.kadu.ia.br/en/en/api/overview/permissions.md)). | | **`METHOD /path`** | The `/v1` endpoint the tool dispatches to, to cross-reference with the [API reference](https://docs.kadu.ia.br/en/en/api/v1). | ## Common arguments[​](#common-arguments "Direct link to Common arguments") These appear across many tools and always mean the same thing. | Argument | Type | Description | | ----------------------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `cursor` | string | Opaque cursor from `meta.next_cursor` of the previous page. See [Pagination](https://docs.kadu.ia.br/en/en/mcp/overview/pagination.md). | | `limit` | integer | Page size, 1 to 100. Default `25`. | | `sort_by` | string | Sort field: `created_at` (default), `updated_at` or one of the filter fields. | | `sort_order` | `asc` \| `desc` | Direction. Default `desc`. | | `_gte` / `_lte` | string | Range (ISO 8601), sent as `field[gte]` / `field[lte]`. | | `idempotency_key` | string | Sent as `X-Idempotency-Key`: repeating the write with the same key does not duplicate it (24 h). See [Idempotency](https://docs.kadu.ia.br/en/en/mcp/overview/idempotency.md). | | `custom_metadata` | object | Free-form JSON owned by you, echoed on every read and webhook. | ## The catalogue[​](#the-catalogue "Direct link to The catalogue") | Family | Tools | read | write | destructive | | ------------------------------------------------------------------------------------------------ | ----- | ---- | ----- | ----------- | | [`me` — Connection identity](https://docs.kadu.ia.br/en/en/mcp/tools/organization.md) | 1 | 1 | 0 | 0 | | [`organizations` — Organization](https://docs.kadu.ia.br/en/en/mcp/tools/organization.md) | 6 | 3 | 2 | 1 | | [`users` — Users](https://docs.kadu.ia.br/en/en/mcp/tools/organization.md) | 1 | 1 | 0 | 0 | | [`memberships` — Memberships](https://docs.kadu.ia.br/en/en/mcp/tools/organization.md) | 1 | 1 | 0 | 0 | | [`roles` — Roles](https://docs.kadu.ia.br/en/en/mcp/tools/organization.md) | 2 | 1 | 1 | 0 | | [`workspaces` — Workspaces](https://docs.kadu.ia.br/en/en/mcp/tools/tasks.md) | 5 | 2 | 2 | 1 | | [`activities` — Activities](https://docs.kadu.ia.br/en/en/mcp/tools/tasks.md) | 5 | 2 | 2 | 1 | | [`tasks` — Tasks](https://docs.kadu.ia.br/en/en/mcp/tools/tasks.md) | 10 | 4 | 2 | 4 | | [`executions` — Executions](https://docs.kadu.ia.br/en/en/mcp/tools/executions.md) | 8 | 6 | 0 | 2 | | [`todos` — Todos](https://docs.kadu.ia.br/en/en/mcp/tools/executions.md) | 1 | 0 | 1 | 0 | | [`memories` — Memories](https://docs.kadu.ia.br/en/en/mcp/tools/executions.md) | 5 | 2 | 2 | 1 | | [`environments` — Environments](https://docs.kadu.ia.br/en/en/mcp/tools/configuration.md) | 5 | 2 | 2 | 1 | | [`secrets` — Secrets](https://docs.kadu.ia.br/en/en/mcp/tools/configuration.md) | 5 | 2 | 2 | 1 | | [`schedules` — Schedules](https://docs.kadu.ia.br/en/en/mcp/tools/configuration.md) | 5 | 2 | 2 | 1 | | [`files` — Files](https://docs.kadu.ia.br/en/en/mcp/tools/configuration.md) | 5 | 3 | 2 | 0 | | [`repository` — Playbook](https://docs.kadu.ia.br/en/en/mcp/tools/repository.md) | 2 | 1 | 1 | 0 | | [`usage` — Usage and cost](https://docs.kadu.ia.br/en/en/mcp/tools/repository.md) | 1 | 1 | 0 | 0 | | [`webhooks` — Webhooks](https://docs.kadu.ia.br/en/en/mcp/tools/webhooks.md) | 6 | 2 | 0 | 4 | | [`webhook_deliveries` — Webhook deliveries](https://docs.kadu.ia.br/en/en/mcp/tools/webhooks.md) | 3 | 2 | 0 | 1 | | [`operation_approvals` — Approvals](https://docs.kadu.ia.br/en/en/mcp/tools/approvals.md) | 2 | 2 | 0 | 0 | | [`system_events` — System events](https://docs.kadu.ia.br/en/en/mcp/tools/approvals.md) | 1 | 1 | 0 | 0 | A tool you cannot run is not listed by `tools/list` at all. If one from this page is missing, see [Authentication](https://docs.kadu.ia.br/en/en/mcp/overview/authentication.md). --- # Playbook and usage The organization's skills repository (the Playbook) and its sync, and the usage and cost of executions. ## Families on this page[​](#families-on-this-page "Direct link to Families on this page") | Family | Tools | read | write | destructive | | ------------------------ | ----- | ---- | ----- | ----------- | | `repository` — Playbook | 2 | 1 | 1 | 0 | | `usage` — Usage and cost | 1 | 1 | 0 | 0 | ## `repository`[​](#repository "Direct link to repository") | Tool | Kind | Scope | Permission | Endpoint | What it does | | ---------------------- | ----- | --------------------------- | ----------------------- | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | `kadu_repository_get` | read | `kadu/repository` + `read` | `agent.repository.read` | `GET /v1/repository` | Gets the organization Kadu Playbook (its skills repository): URL, connection, last sync and the catalog of skills that can become tasks. | | `kadu_repository_sync` | write | `kadu/repository` + `write` | `agent.repository.sync` | `POST /v1/repository/sync` | Queues a sync of the Kadu Playbook: refreshes the skill catalog and applies the tasks, schedules, environments and webhooks it declares. | ## `usage`[​](#usage "Direct link to usage") | Tool | Kind | Scope | Permission | Endpoint | What it does | | ---------------- | ---- | --------------------- | ------------------ | --------------- | --------------------------------------------------------------------------------------------------- | | `kadu_usage_get` | read | `kadu/usage` + `read` | `agent.usage.read` | `GET /v1/usage` | Usage and cost of the executions in a period, grouped by tier, workspace, activity, task or source. | *** The fields of each resource are in the [API reference](https://docs.kadu.ia.br/en/en/api/v1). A tool you cannot run is not listed by `tools/list` at all. If one from this page is missing, see [Authentication](https://docs.kadu.ia.br/en/en/mcp/overview/authentication.md). --- # Structure and tasks Workspaces, activities and tasks — the tree that organizes what the agent does —, running a task, the pre-check and the HTTP trigger endpoint. ## Families on this page[​](#families-on-this-page "Direct link to Families on this page") | Family | Tools | read | write | destructive | | ------------------------- | ----- | ---- | ----- | ----------- | | `workspaces` — Workspaces | 5 | 2 | 2 | 1 | | `activities` — Activities | 5 | 2 | 2 | 1 | | `tasks` — Tasks | 10 | 4 | 2 | 4 | ## `workspaces`[​](#workspaces "Direct link to workspaces") | Tool | Kind | Scope | Permission | Endpoint | What it does | | ------------------------ | ----------- | ---------------------------- | ------------------------- | ---------------------------- | ---------------------------------------------------------------------------------------- | | `kadu_workspaces_list` | read | `kadu/workspaces` + `read` | `agent.workspaces.list` | `GET /v1/workspaces` | Lists the workspaces of the organization (the top level of Workspace > Activity > Task). | | `kadu_workspaces_get` | read | `kadu/workspaces` + `read` | `agent.workspaces.read` | `GET /v1/workspaces/{id}` | Gets a workspace. | | `kadu_workspaces_create` | write | `kadu/workspaces` + `write` | `agent.workspaces.create` | `POST /v1/workspaces` | Creates a workspace. | | `kadu_workspaces_update` | write | `kadu/workspaces` + `write` | `agent.workspaces.update` | `PATCH /v1/workspaces/{id}` | Updates a workspace. | | `kadu_workspaces_delete` | destructive | `kadu/workspaces` + `delete` | `agent.workspaces.delete` | `DELETE /v1/workspaces/{id}` | Deletes a workspace together with its activities and tasks. | ## `activities`[​](#activities "Direct link to activities") | Tool | Kind | Scope | Permission | Endpoint | What it does | | ------------------------ | ----------- | ---------------------------- | ------------------------- | ---------------------------- | ---------------------------------------------------------- | | `kadu_activities_list` | read | `kadu/activities` + `read` | `agent.activities.list` | `GET /v1/activities` | Lists the activities (groups of tasks inside a workspace). | | `kadu_activities_get` | read | `kadu/activities` + `read` | `agent.activities.read` | `GET /v1/activities/{id}` | Gets an activity. | | `kadu_activities_create` | write | `kadu/activities` + `write` | `agent.activities.create` | `POST /v1/activities` | Creates an activity inside a workspace. | | `kadu_activities_update` | write | `kadu/activities` + `write` | `agent.activities.update` | `PATCH /v1/activities/{id}` | Updates an activity. | | `kadu_activities_delete` | destructive | `kadu/activities` + `delete` | `agent.activities.delete` | `DELETE /v1/activities/{id}` | Deletes an activity together with its tasks. | ## `tasks`[​](#tasks "Direct link to tasks") | Tool | Kind | Scope | Permission | Endpoint | What it does | | ---------------------------- | ----------- | ----------------------- | ---------------------- | -------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | | `kadu_tasks_list` | read | `kadu/tasks` + `read` | `agent.tasks.list` | `GET /v1/tasks` | Lists the tasks: each one runs a skill (`:`) of the organization repository as the agent. | | `kadu_tasks_get` | read | `kadu/tasks` + `read` | `agent.tasks.read` | `GET /v1/tasks/{id}` | Gets a task, with its parameters schema and whether it is conversational. | | `kadu_tasks_create` | write | `kadu/tasks` + `write` | `agent.tasks.create` | `POST /v1/tasks` | Creates a task that runs a skill of the organization repository. | | `kadu_tasks_update` | write | `kadu/tasks` + `write` | `agent.tasks.update` | `PATCH /v1/tasks/{id}` | Updates a task. | | `kadu_tasks_delete` | destructive | `kadu/tasks` + `delete` | `agent.tasks.delete` | `DELETE /v1/tasks/{id}` | Deletes a task. | | `kadu_tasks_run` | destructive | `kadu/tasks` + `write` | `agent.executions.run` | `POST /v1/tasks/{id}/run` | Runs a task: queues an execution of the financial agent in an environment. | | `kadu_tasks_readiness_get` | read | `kadu/tasks` + `read` | `agent.tasks.read` | `GET /v1/tasks/{id}/readiness` | Pre-check of a run: whether the task can be run now in the environment (plan, cost limit, missing secrets, repository). | | `kadu_tasks_endpoint_get` | read | `kadu/tasks` + `read` | `agent.tasks.read` | `GET /v1/tasks/{id}/endpoint` | Gets the task's HTTP trigger endpoint: URL, whether it is enabled and the auth type (credentials are never returned). | | `kadu_tasks_endpoint_update` | destructive | `kadu/tasks` + `write` | `agent.tasks.update` | `PUT /v1/tasks/{id}/endpoint` | Configures the HTTP endpoint that lets a third party trigger the task, with its credentials. | | `kadu_tasks_endpoint_delete` | destructive | `kadu/tasks` + `delete` | `agent.tasks.update` | `DELETE /v1/tasks/{id}/endpoint` | Disables the task's HTTP trigger endpoint (the configuration is kept). | *** The fields of each resource are in the [API reference](https://docs.kadu.ia.br/en/en/api/v1). A tool you cannot run is not listed by `tools/list` at all. If one from this page is missing, see [Authentication](https://docs.kadu.ia.br/en/en/mcp/overview/authentication.md). --- # Webhooks The webhooks that tell your systems what happens in Kadu, the signing secret and the delivery history, with replay. ## Families on this page[​](#families-on-this-page "Direct link to Families on this page") | Family | Tools | read | write | destructive | | ----------------------------------------- | ----- | ---- | ----- | ----------- | | `webhooks` — Webhooks | 6 | 2 | 0 | 4 | | `webhook_deliveries` — Webhook deliveries | 3 | 2 | 0 | 1 | ## `webhooks`[​](#webhooks-1 "Direct link to webhooks-1") | Tool | Kind | Scope | Permission | Endpoint | What it does | | ----------------------------- | ------------------------------------ | -------------------------- | ----------------------- | -------------------------------------- | ---------------------------------------------------------------------- | | `kadu_webhooks_list` | read | `kadu/webhooks` + `read` | `agent.webhooks.list` | `GET /v1/webhooks` | Lists the webhooks the organization registered. | | `kadu_webhooks_get` | read | `kadu/webhooks` + `read` | `agent.webhooks.read` | `GET /v1/webhooks/{id}` | Gets a webhook (credentials and signing secret are never returned). | | `kadu_webhooks_create` | destructive | `kadu/webhooks` + `write` | `agent.webhooks.create` | `POST /v1/webhooks` | Registers a webhook: Kadu will POST the subscribed events to this URL. | | `kadu_webhooks_update` | destructive (depending on arguments) | `kadu/webhooks` + `write` | `agent.webhooks.update` | `PATCH /v1/webhooks/{id}` | Updates a webhook. | | `kadu_webhooks_delete` | destructive | `kadu/webhooks` + `delete` | `agent.webhooks.delete` | `DELETE /v1/webhooks/{id}` | Deletes a webhook. | | `kadu_webhooks_rotate_secret` | destructive | `kadu/webhooks` + `write` | `agent.webhooks.update` | `POST /v1/webhooks/{id}/rotate-secret` | Rotates the signing secret of a webhook. | ## `webhook_deliveries`[​](#webhook_deliveries "Direct link to webhook_deliveries") | Tool | Kind | Scope | Permission | Endpoint | What it does | | -------------------------------- | ----------- | ----------------------------------- | --------------------------------- | ----------------------------------------- | ---------------------------------------------------------------------------- | | `kadu_webhook_deliveries_list` | read | `kadu/webhook_deliveries` + `read` | `agent.webhook_deliveries.list` | `GET /v1/webhook_deliveries` | Lists the 50 most recent webhook deliveries. | | `kadu_webhook_deliveries_get` | read | `kadu/webhook_deliveries` + `read` | `agent.webhook_deliveries.read` | `GET /v1/webhook_deliveries/{id}` | Gets a webhook delivery: payload, attempts and the response of the receiver. | | `kadu_webhook_deliveries_replay` | destructive | `kadu/webhook_deliveries` + `write` | `agent.webhook_deliveries.replay` | `POST /v1/webhook_deliveries/{id}/replay` | Queues a webhook delivery again, to the same webhook only. | *** The fields of each resource are in the [API reference](https://docs.kadu.ia.br/en/en/api/v1). A tool you cannot run is not listed by `tools/list` at all. If one from this page is missing, see [Authentication](https://docs.kadu.ia.br/en/en/mcp/overview/authentication.md). --- # Kadu Playbook The **Kadu Playbook** is the git repository where an organization keeps everything Kadu runs for it: the tasks, the helper skills they use and the organization's configuration. Each organization has one Playbook, and it follows the **Kadu Playbook spec**, described in this section. The Playbook is **Kadu's source of truth**: each push to the default branch updates the console's tasks, and each execution reads the skills from there. This section is the guide to setting up and maintaining a Playbook. It follows what Kobana's own Playbook (`kadu-kobs-kobana`) learned in production. ## The model[​](#the-model "Direct link to The model") ``` organization = Playbook (a git repository) workspace = plugin inside the Playbook task = skill with a `task:` block → skill_slug = : ``` | In Kadu | In the Playbook | Example | | ----------------------------------------- | ------------------------------- | --------------------------------------------------------------- | | Kobana organization | the `kadu-kobs-kobana` Playbook | — | | Finance workspace | the `finance/` plugin | `finance/.claude-plugin/plugin.json` | | Task "Settle payments via boleto and Pix" | the skill with a `task:` block | `finance/skills/task-baixar-pagamentos-via-boleto-pix/SKILL.md` | | Helper skill | a skill without a `task:` block | `finance/skills/fintera-listar-contas-a-receber/SKILL.md` | | Environments, webhooks, metadata | the root `kadu.yaml` | `kadu.yaml` | ## What you will do[​](#what-you-will-do "Direct link to What you will do") | Step | Page | | -------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | | Create the Playbook structure: catalog, plugins, skills | [Playbook structure](https://docs.kadu.ia.br/en/en/playbook/estrutura.md) | | Link the Playbook to the organization | [Connect the Playbook](https://docs.kadu.ia.br/en/en/playbook/conectar.md) | | Understand how a push becomes a task in the console | [Sync](https://docs.kadu.ia.br/en/en/playbook/sincronizacao.md) | | Declare the task in the frontmatter | [The `task:` block](https://docs.kadu.ia.br/en/en/playbook/bloco-task.md) | | Schedule, expose over HTTP, choose a model | [Schedule, endpoint and executor](https://docs.kadu.ia.br/en/en/playbook/agendamento-endpoint-executor.md) | | Write the task body with the console instrumentation | [The task body](https://docs.kadu.ia.br/en/en/playbook/corpo-da-tarefa.md) | | Declare and provision credentials | [Secrets](https://docs.kadu.ia.br/en/en/playbook/secrets.md) | | Declare the organization's environments, webhooks and metadata | [The `kadu.yaml`](https://docs.kadu.ia.br/en/en/playbook/kadu-yaml.md) | | Test locally and as CI | [Local development](https://docs.kadu.ia.br/en/en/playbook/desenvolvimento-local.md) | | Write skills that work with nobody watching | [Best practices](https://docs.kadu.ia.br/en/en/playbook/boas-praticas.md) | | A task with tools the runner does not have | [Custom workflow](https://docs.kadu.ia.br/en/en/playbook/workflow-proprio.md) | | Check before merging | [Checklist](https://docs.kadu.ia.br/en/en/playbook/checklist.md) | | Diagnose what went wrong | [Common errors](https://docs.kadu.ia.br/en/en/playbook/erros-comuns.md) | ## Three rules that apply to everything[​](#three-rules-that-apply-to-everything "Direct link to Three rules that apply to everything") 1. **Merging to `main` is a deploy.** Each execution reads the default branch at the moment it starts. There is no build, release or canary: a broken `main` breaks every task in the organization immediately, and rollback is `git revert`. 2. **The Playbook rules the task.** Every field declared in the `task:` block overwrites what is in the console on each sync. Change the file, not the console. 3. **No secret value goes into the Playbook.** The Playbook declares only the credential **names**. The values live in Kadu's vault (production) or in a local `.env` outside git (development). ## The `kadu` plugin[​](#the-kadu-plugin "Direct link to the-kadu-plugin") Besides the organization's plugins, every execution loads the **`kadu`** plugin, maintained by Kobana. It brings the console instrumentation (events, logs, pending items, result), the wrapper that runs the task (`/kadu:agent-run-task`) and utilities such as `/kadu:minhareceita-buscar-cnpj`. Outside a Kadu execution, the plugin runs in simulation mode (dry-run): the instrumentation is only printed in the terminal. See [Local development](https://docs.kadu.ia.br/en/en/playbook/desenvolvimento-local.md). That is why **no organization plugin can be named `kadu`**: the name already belongs to the instrumentation plugin, and Kadu finds the right plugin by the `skill_slug` prefix. *** The Playbook format was inspired by the Claude Code plugin format, which is why the names of some files (`.claude-plugin/`, `SKILL.md`) come from it. --- # Schedule, endpoint and executor A task's configuration also lives in the repository: when it runs on its own (`schedules`), whether another system can trigger it (`endpoint`), which model it uses (`executor`) and free-form metadata (`metadata`). ## Schedules (`schedules`)[​](#schedules-schedules "Direct link to schedules-schedules") Each item schedules the task in an environment. The environment is matched by **exact name**: the one created in the console or the one declared in [`kadu.yaml`](https://docs.kadu.ia.br/en/en/playbook/kadu-yaml.md), which the sync applies first. ``` schedules: - environment: Produção cron: "0 9 * * 1-5" timezone: America/Sao_Paulo - environment: Sandbox cron: "0 12 * * 1" paused: true ``` | Key | Required | Effect | | ------------- | -------- | ------------------------------------------------------------------------------------------------------------------------ | | `environment` | yes | Exact name of the environment. One schedule per task and environment. | | `cron` | yes | Five-field expression. | | `timezone` | no | IANA time zone (`America/Sao_Paulo`). **Missing = UTC**, even on a schedule that already existed with another time zone. | | `paused` | no | `true` pauses, `false` resumes. **Missing = do not touch**: a pause made in the console survives the sync. | The sync distinguishes the schedule **it created** from the one created in the console: | Situation | What the sync does | | --------------------------------------------------------- | -------------------------------------------------------- | | Environment declared, no schedule | creates it, marked as the repository's | | Schedule deleted in the console, environment declared | recreates it with the frontmatter values | | Existing schedule (from any origin), environment declared | updates `cron` and `timezone`; `paused` only if declared | | **Repository's** schedule, environment outside the list | deletes it | | **Console's** schedule, environment outside the list | does not touch it | A missing `schedules` touches nothing. `schedules: []` removes the schedules the repository created and leaves the console's. **One item with an error discards the whole list for that task**, with a warning: an item without `cron`, `paused: "sim"`, a repeated environment, an environment that does not exist, an invalid cron or time zone. This is on purpose: applying only the good items would delete the schedule of the item with the typo. While the warning is there, the task's schedules stay as they were. A schedule **does not stack**: with the task already running at the scheduled time, the run is skipped. A cron shorter than the task's duration loses runs. ## Run endpoint (`endpoint`)[​](#run-endpoint-endpoint "Direct link to run-endpoint-endpoint") Makes the task triggerable by another system, with `POST /run-task/{task_id}` and a JSON body. The block declares **only** the state and the authentication type: ``` endpoint: enabled: true auth: bearer # basic | bearer | header | params ``` The credential (token, password, header value) is stored encrypted in Kadu and **never** goes into the repository. An extra key in the block, such as `token:`, is ignored. The sync reconciles both sides, and the endpoint is **never turned on without a credential**: | State in Kadu | Frontmatter | What the sync does | | --------------------- | ---------------------------- | -------------------------------------------------------------------------------------- | | no configuration | `enabled: false` | nothing | | no configuration | `enabled: true` | creates the configuration **turned off**, with only the authentication type, and warns | | complete credential | `enabled: true`, same `auth` | turns on | | incomplete credential | `enabled: true`, same `auth` | keeps it off and warns on every sync | | stored `auth` differs | `enabled: true` | does not touch it and warns: changing the type would delete the credential | | any | `enabled: false` | turns off, without deleting the credential | The path for a new task: push with `enabled: true` → the sync creates the configuration turned off and warns → the Kobana team registers the credential → the next sync turns it on. After that, the credential can be changed in **Settings → Endpoints**. Like every field in the block, the repository rules: an endpoint turned off by hand in the console turns back on at the next push as long as the frontmatter says `enabled: true`. A block without `enabled` or `auth`, or with an `auth` outside the list, is discarded entirely. Two effects on the skill body: * **a run through the endpoint brings no file**, even with `accepts_input_files: true`: the call body is JSON only. If the task accepts both paths, treat "no file" as a normal case; * `environment_id` is required on this path, and a secret in `required_secrets` with no value in the environment rejects the run with `422 missing_secrets` before creating the execution. The call contract is in [Run endpoint](https://docs.kadu.ia.br/en/en/api/overview/run-task-endpoint.md). ### Task triggered by the Autonomous Inbox[​](#task-triggered-by-the-autonomous-inbox "Direct link to Task triggered by the Autonomous Inbox") The endpoint directly receives Kobana's Autonomous Inbox webhook. The received email becomes the parameters, **filtered by the properties of `parameters_schema`**, and the delivery date becomes the reference date. Minimum schema for a task triggered by the Inbox: ``` { "type": "object", "required": ["message_id"], "properties": { "dry_run": { "type": "boolean", "default": false }, "message_id": { "type": "string" }, "download_url": { "type": ["string", "null"] } }, "additionalProperties": false } ``` ## Executor and model (`executor`)[​](#executor-and-model-executor "Direct link to executor-and-model-executor") `model` chooses the tier (price, executor and model). `executor` overrides only the executor and the AI model, on top of the tier. The price remains the tier's. ``` executor: cli: claude_code # claude_code | goose model: claude-opus-4-8 # claude_code: claude-* or the aliases sonnet, opus, haiku ``` `executor: inherit` clears the override and goes back to inheriting from the tier. Missing does not touch it, including an override stored by the Kobana team. `claude_code` with a non-Anthropic model is not applied and becomes a warning. Use sparingly: a task with an override does not follow the tier's model change, and the cost is no longer predictable from the tier. ## Metadata (`metadata`)[​](#metadata-metadata "Direct link to metadata-metadata") A free-form object stored whole in the task's `custom_metadata`, visible in the v1 API and in webhooks. When declared, it replaces the whole object; when missing, it does not touch it. ``` metadata: dono: financeiro centro_de_custo: "42" ``` --- # The `task:` block A skill becomes a task when it declares the `task:` block in the `SKILL.md` frontmatter. Make a task only of what someone needs to **trigger**: a step reusable by other skills is a helper, and a good task is usually the orchestration of several helpers. ``` --- name: task-meu-processo description: What the skill does, in one line (used in search) user-invocable: true task: label: "Meu Processo" description: "The agent's introduction text to the user." workspace: Financeiro activity: Baixa de Recebimentos model: tier-3 parameters_schema: null parameters_sensitive: false accepts_input_files: false generates_output_files: false systems: - Kobana features: - What the task covers required_secrets: - KOBANA_ACCESS_TOKEN endpoint: # optional enabled: true auth: bearer schedules: # optional: one schedule per environment - environment: Produção cron: "0 9 * * 1-5" timezone: America/Sao_Paulo executor: # optional: executor and model override, or `inherit` cli: claude_code model: claude-opus-4-8 metadata: # optional: free-form object dono: financeiro dependencies: # optional apt: [ffmpeg] services: [postgres] --- ``` ## Field by field[​](#field-by-field "Direct link to Field by field") | Field | Type | Effect | | ------------------------ | ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `label` | string | Name in the console. Without it, the task is created with the `skill_slug` as its name. | | `description` | string | The agent's introduction text on the task screen. | | `workspace` | string | Workspace name **when the sync creates it**. The task always stays in the plugin's workspace. See [the name trap](#the-workspace-and-activity-name-trap). | | `activity` | string | **Required** for the task to be created. Grouping within the workspace, matched by exact name. | | `model` | string | Slug of a **tier** (`tier-3`), not of an AI model. The tier defines price, executor and model. | | `parameters_schema` | JSON Schema or `null` | Validates the parameters **before** creating the execution. It is not decorative. | | `parameters_sensitive` | boolean | `true` encrypts the stored parameters and hides them in the interface. Use it when the run carries personal data. | | `accepts_input_files` | boolean | `true` accepts attachments at run time; the paths arrive in `$INPUT_FILES`. | | `generates_output_files` | boolean | `true` publishes everything the skill saves in `$OUTPUT_FILES_DIR`. | | `systems` | list of strings | Display only: the external systems the task touches. | | `features` | list of strings | Display only: what the task delivers. | | `required_secrets` | list of strings | **Allowlist**: only what is here reaches the execution. See [Secrets](https://docs.kadu.ia.br/en/en/playbook/secrets.md). | | `endpoint` | `{ enabled, auth }` | Trigger through `POST /run-task/{task_id}`. See [Schedule, endpoint and executor](https://docs.kadu.ia.br/en/en/playbook/agendamento-endpoint-executor.md). | | `schedules` | list | The task's schedules, one per environment. | | `executor` | `inherit` or `{ cli, model }` | Executor and model override on top of the tier. | | `metadata` | object | Stored whole in the task's `custom_metadata` (appears in the API and in webhooks). | | `conversational` | boolean | `true` lets the person request changes after the execution finishes, in a new turn that continues the previous one. Works on both executors. See [Conversational tasks](https://docs.kadu.ia.br/en/en/playbook/corpo-da-tarefa.md#conversational-tasks). | | `dependencies` | `{ apt, services }` | System packages and databases Kadu installs before the execution. Names from Kadu's list only. See [`dependencies`](https://docs.kadu.ia.br/en/en/playbook/dependencias.md). | :::danger A wrong type is silently discarded The sync only accepts a string where it expects a string, a boolean where it expects a boolean and a list of strings in the lists. `parameters_sensitive: "true"` (with quotes) becomes "not declared", with no error anywhere. And "not declared", on a task that already exists, means **"do not touch"**: the field keeps the old value and the change does not take effect. Check "Tasks updated" after the merge. ::: ## `model`: the slug of a tier, not of a model[​](#model-the-slug-of-a-tier-not-of-a-model "Direct link to model-the-slug-of-a-tier-not-of-a-model") `task.model` points to a **tier** registered in Kadu. The tier carries the price, the executor and the AI model. Tiers in the initial registry: | Tier | Executor | AI model | | -------- | ------------ | ------------------------------------ | | `tier-1` | goose | `openrouter/openai/gpt-4.1-mini` | | `tier-2` | goose | `openrouter/google/gemini-3.5-flash` | | `tier-3` | claude\_code | `claude-sonnet-4-6` | | `tier-4` | claude\_code | `claude-opus-4-8` | | `tier-5` | claude\_code | `claude-fable-5` | Tiers may change. Confirm with Kobana before choosing a new tier. Writing a model id (`claude-sonnet-4-6`) does not break the sync, but it is not applied: a new task is created in the lowest-order tier, an existing one stays as it is, and the result warns `model "…" não é um tier ativo`. Without `model`, the effect is the same, with no warning. Choose by the work: reading code, planning and review call for `tier-4`; orchestration with decisions calls for `tier-3`. `tier-1` and `tier-2` switch the executor to Goose: use them only with a skill tested on it, because Goose's helpers and wrapper are different. ## `parameters_schema`: actually validated[​](#parameters_schema-actually-validated "Direct link to parameters_schema-actually-validated") The schema is compiled with Ajv (`strict: false`, with `ajv-formats`) and runs against the parameters before creating the execution. A well-written schema rejects a wrong run without spending an execution. The console also builds the run form from it. ``` parameters_schema: type: object additionalProperties: false # rejects a parameter you did not anticipate required: - repo_url properties: repo_url: type: string minLength: 3 description: "owner/repo or full URL" priority: type: integer enum: [0, 1, 2, 3, 4] default: 0 ``` * `additionalProperties: false` turns a typo into a run error, instead of an ignored parameter. * `required` only for what actually blocks the execution. * `default` is documentation for whoever triggers the run: it does **not** fill in the value. Apply the default in the skill body (`jq -r '.priority // 0'`). * `null` when the task takes no parameters at all. ## `dependencies`: packages and databases for the execution[​](#dependencies-packages-and-databases-for-the-execution "Direct link to dependencies-packages-and-databases-for-the-execution") The agent runs without administrator rights. A task that needs a system package or a database declares it, and Kadu installs it before the execution starts: ``` dependencies: apt: [ffmpeg, poppler-utils] services: [postgres] ``` Names from the list maintained by Kobana only: PDF, OCR, documents, spreadsheets, images, audio and video, files, database clients and the `postgres`, `mysql`, `mariadb`, `redis` and `valkey` services. The full list, the rules and what to do when something is missing are in [Dependencies](https://docs.kadu.ia.br/en/en/playbook/dependencias.md). ## The `workspace` and `activity` name trap[​](#the-workspace-and-activity-name-trap "Direct link to the-workspace-and-activity-name-trap") Matching is by **exact name**: `Desenvolvimento`, `desenvolvimento` and `Desenvolvimento `(with a trailing space) are three different names. | | Effect on the sync | | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | `workspace` | **decides nothing** for an existing task: the workspace is the plugin's. The name only names the workspace the first time it is created. | | `activity` | matched by exact name within the plugin's workspace. A name that does not exist **creates a new activity and moves the task to it**. | A typo in `activity:` does not fail: an activity "Baixa de recebimentos" appears next to "Baixa de Recebimentos", with the task inside the new one. The sync only creates activities, never renames or deletes them, so the duplicate stays until someone removes it. Copy the name of the activity that already exists instead of typing it from memory. To **rename** an activity, rename it in Kadu first and then in the frontmatter of every skill that uses it. In the reverse order, the sync creates the new one and the tasks scatter between the two. ## Real example[​](#real-example "Direct link to Real example") The smoke task in Kobana's repository declares parameters, input and output files, a secret and the endpoint: ``` task: label: "Testar orquestração" description: "I am the platform's test task. I go through each tab of the execution and read what you attach at run time." workspace: Kadu activity: Teste model: tier-3 parameters_schema: type: object additionalProperties: false properties: eco: type: string maxLength: 200 description: "Text returned in the Result — proves the parameter reached the execution" falhar: type: boolean default: false description: "true ends at step 6 with an error, to exercise the failure path" parameters_sensitive: false accepts_input_files: true generates_output_files: true systems: - Kadu features: - "Timeline: one event at each level — thinking, info, success, warning and error" - "Pending: creates two, with payload" required_secrets: - KADU_SMOKE_SECRET endpoint: enabled: true auth: bearer ``` --- # Best practices A skill's value lies in what it knows and a model would not guess: the right endpoint, the field that comes back null, the business rule that is written nowhere. The practices below come from the skills Kobana runs in production. ## When writing a skill[​](#when-writing-a-skill "Direct link to When writing a skill") **Verify before asserting.** Run the command, call the API, read the file. Every field table, enum and error message in a skill body should have come from a real run, not from memory. When a check contradicts what you were about to write, the finding is the check. **Name the concrete case.** "Kobana's MCP responds `401` to a personal token with **REST API** usage: `KOBANA_MCP_TOKEN` must be a token with **MCP** usage" is worth more than "check the token". The agent follows an example better than a general rule. **Explain the why of every prohibition.** A guardrails table with the reason next to each rule. A rule without a reason is ignored in the first rush, by the agent and by whoever edits the skill later. | Don't | Why | | -------------------------------------------------------------------- | ------------------------------------------------------------------------ | | Ask the user during the execution | there is no user: the execution hangs until the 60-minute limit | | Call `log_http_call` on every request of a scanner | thousands of lines drown the Logs tab and hide the integration calls | | Read configuration with `${VAR:-default}` outside `required_secrets` | the default always applies, and nobody can change the value through Kadu | **Write for both modes.** A skill that is useful in Claude Code runs in `local` (with a person answering) and in `task` (in Kadu, with nobody). In `task`, do not ask: record a pending item and move on. Detect the mode with `GITHUB_ACTIONS` ([The task body](https://docs.kadu.ia.br/en/en/playbook/corpo-da-tarefa.md)). **End with a common errors table:** symptom → cause → where the solution is. It is the part that saves the most time for whoever diagnoses. **A declared parameter must be read and used.** A parameter in `parameters_schema` that no step consults is a bug, not documentation. **A task orchestrates; helpers do.** Separate access to each external system into small helper skills (`fintera-listar-contas-a-receber`, `nfeio-buscar-nota-fiscal`) and let the task decide the order and what to do with each result. The helpers are reused by other tasks and tested on their own in Claude Code. ## When changing a skill that is already a task[​](#when-changing-a-skill-that-is-already-a-task "Direct link to When changing a skill that is already a task") * **Change the frontmatter, never the folder name.** Renaming creates another task. * **Update the repository documentation in the same commit**: the workspace's task table in `README.md` and the task count. An outdated README is the first place the next person gets lost. * **Check the sync warnings** after the merge, through the **Sync** button. An invalid item (cron, environment name) discards the whole list, and only the warning says so. * **Task configuration changes in the repository, not in the console.** Schedules, endpoint, executor and `metadata` declared in the `task:` block, and environments in `kadu.yaml`, revert to what was declared on every push. ## When maintaining the Playbook[​](#when-maintaining-the-playbook "Direct link to When maintaining the Playbook") * **`main` is production.** Work on a branch, validate in the CI sandbox and treat the merge as a deploy ([Sync](https://docs.kadu.ia.br/en/en/playbook/sincronizacao.md)). * **Add files by explicit path.** `git add -A` picks up the `.execution-result.json` that a local run leaves at the root and the test `.env`. Keep both in `.gitignore`. * **Keep a `CLAUDE.md` at the root** with the repository's rules: how skills are written, what cannot go into git, the order for provisioning a secret. It is what Claude Code reads before editing. ## Language[​](#language "Direct link to Language") Write the skills, events and result in the language of whoever will read them. An execution's events and result should follow `$LOCALE`, the language of whoever triggered the run. --- # Checklist before merging ## Frontmatter[​](#frontmatter "Direct link to Frontmatter") * [ ] `task:` block with `label`, `workspace` and `activity`. Without `activity`, the sync does not create the task. * [ ] `activity` with the **exact** name of the activity that already exists. Otherwise the sync creates another one and moves the task. * [ ] `model` is the slug of an active tier (`tier-N`). * Final folder name: renaming it later creates another task. * Right types in the YAML: `true`, not `"true"`. A wrong type is silently discarded. * [ ] `parameters_schema` with `additionalProperties: false`, or `null` if the task takes no parameters. * Complete `required_secrets`, including configuration ids. * [ ] `schedules` with the **exact** name of an existing environment (or one declared in `kadu.yaml`) and the cron checked. `timezone` declared if the time is not UTC. ## Body[​](#body "Direct link to Body") * Starts with `step.started` and `mark_body_started`. * Every event has `type` **and** `message`. * Every call to an external API goes through `log_http_call`; bulk tool traffic goes summarized in `append_log`. * Helpers that do the bulk of the work instrument their own steps. * Every configuration variable read in the body is in `required_secrets`. * No question to the user on the automatic path. * Ambiguity and failure become pending items, not a stop. * [ ] `.execution-result.json` with `result` in Markdown and `summary` with counters, also written on the error path. * No secret value in an event, log, pending item or result. ## Validation[​](#validation "Direct link to Validation") * Ran in simulation (`/kadu:agent-run-task :`). * Passed the CI sandbox smoke test. * Repository `README.md` updated (the workspace's task table). ## After the merge[​](#after-the-merge "Direct link to After the merge") * [ ] **Sync** with no warnings for the skill; task under "Tasks created" or "Tasks updated" with the expected fields. * Secrets with a value in the environment, before the first scheduled time. * Endpoint credential registered, if the task declares `endpoint`. * First run checked: timeline, logs, pending items and result. --- # Connect the Playbook Kadu reads the Playbook in two ways: through **Kadu's GitHub App** (recommended) or through an SSH **deploy key**. In both cases the access is read-only. ## Kadu's GitHub App (recommended)[​](#kadus-github-app-recommended "Direct link to Kadu's GitHub App (recommended)") This is the path of the first console sign-in: the **Connect your Playbook** card leads to the installation. See [Getting started](https://docs.kadu.ia.br/en/en/primeiros-passos.md). | | With the GitHub App | | ------------- | ----------------------------------------------------------------------------------------------------------------- | | Permission | **Contents: Read-only**, only on the repositories chosen at installation | | Reading | an installation token generated on each read, valid for 1 hour, restricted to the organization's repository | | Updating | each push to the default branch notifies Kadu, which syncs right away | | Disconnection | uninstalling the App, suspending the installation or removing the repository from it disconnects the organization | On returning from GitHub, the console confirms that the installation belongs to whoever made it before linking the repository. If the installation covers several repositories, the console asks you to choose which one is the Playbook, marking those with the Playbook catalog, `.claude-plugin/marketplace.json`. ## Deploy key (without the App)[​](#deploy-key-without-the-app "Direct link to Deploy key (without the App)") For a repository maintained by Kobana, or when the App cannot be installed: 1. The Kobana team registers the repository's SSH address on the organization (`git@github.com:/.git`). 2. Kadu generates a key pair and shows the **public key**. 3. On GitHub, under **Settings → Deploy keys → Add deploy key**, paste the public key **without** checking "Allow write access". 4. For updates on every push, configure under **Settings → Webhooks → Add webhook** the URL and secret the Kobana team gives you, with content type `application/json` and the "Just the push event" event. The private key is stored encrypted and never leaves Kadu. Without the webhook, the sync still happens through the hourly check. ## What Kadu stores[​](#what-kadu-stores "Direct link to What Kadu stores") * The repository address and, without the App, the encrypted deploy key. * The **catalog**: the frontmatter of skills with a `task:` block, read on each sync. * The latest commits read, shown in **Settings → Deployment**. Kadu keeps no copy of the skills' code: each execution clones the default branch at the moment it starts and deletes the clone at the end. ## One Playbook per organization[​](#one-playbook-per-organization "Direct link to One Playbook per organization") An organization has a single Playbook, and all its workspaces are in it. For a new business domain, create a new plugin in the same repository ([New workspace](https://docs.kadu.ia.br/en/en/playbook/estrutura.md#new-workspace)), not a new repository. --- # The task body The body of the `SKILL.md` is the instructions the agent follows. In an execution, it runs **inside** the `/kadu:agent-run-task` wrapper, which has already prepared everything: instrumentation helpers, reference date, HTTP call logging and finalization. Do not redo any of that. ## The helpers[​](#the-helpers "Direct link to The helpers") The helpers live in `/tmp/agent_state.sh`. Each shell block in the body starts with `source /tmp/agent_state.sh`. | Helper | Feeds | What for | | ------------------------ | ------------ | --------------------------------------------------------------- | | `emit_event ''` | Timeline tab | one event per milestone of the execution, not in a tight loop | | `append_log ''` | Logs tab | log lines | | `log_http_call ''` | Logs tab | **every** call to an external system's API, with secrets masked | | `create_todo ''` | Pending tab | a pending item that outlives the execution | | `mark_body_started` | — | mandatory sentinel (below) | :::danger Do not redefine the helpers Redefining any of them in the skill body takes the sending off the path that masks secrets. Use the ones the wrapper wrote. ::: ## The sentinel[​](#the-sentinel "Direct link to The sentinel") At the end of the execution, the wrapper checks whether the body marked that it started. Without the mark, it records `execution.body_skipped` and **fails the execution** (exit 2). So the body starts like this: ``` source /tmp/agent_state.sh emit_event "$(jq -nc '{type:"step.started",level:"info",step:"1",message:"Fetching yesterday'"'"'s payments"}')" mark_body_started ``` ## Events: `type` and `message` are required[​](#events-type-and-message-are-required "Direct link to events-type-and-message-are-required") Every event needs `type` **and** `message`; `level`, `step` and `payload` are optional. An event without `message` is rejected (400) and the sender ignores the error: the event **disappears** from the timeline, with nothing in the log. If a step went missing from the timeline, suspect this first. | Key | Convention | | --------- | --------------------------------------------------------------- | | `type` | `step.started` and `step.completed` per step | | `step` | step number as a string (`"1"`, `"2"`) | | `level` | `thinking`, `info`, `success`, `warning` or `error` | | `message` | what is happening, in one sentence, in the execution's language | | `payload` | structured data for the step (counts, ids) | ## Logs and HTTP calls[​](#logs-and-http-calls "Direct link to Logs and HTTP calls") `log_http_call` is for calls to **integration APIs** (Fintera, Pagar.me, Linear, Slack), one entry per call: ``` source /tmp/agent_state.sh RESP=$(curl -sS -w '\n%{http_code}' -u "$FINTERA_TOKEN:X" \ "https://financeiro.fintera.com.br/entities/$FINTERA_ENTITY_ID/receivable_accounts?per_page=50") log_http_call "$(printf 'GET /receivable_accounts?per_page=50\n→ %s' "$(tail -n1 <<<"$RESP")")" ``` A high-volume tool (scanner, crawler, batch load) makes hundreds or thousands of requests: logging each one drowns the Logs tab and hides the calls that matter. Summarize in one `append_log` line (`nuclei: 1,240 requests → 3 findings`) and reserve `log_http_call` for the APIs. ## There is no user in the execution[​](#there-is-no-user-in-the-execution "Direct link to There is no user in the execution") The execution is automatic. Every question is a silent hang: the task stays stuck until it hits the 60-minute limit. Where an interactive skill would ask, the task decides and records: | Situation | What to do | | ----------------------------------- | ----------------------------------------------------------------------------------------------- | | Ambiguous data | pick the most likely reading, say which one you picked and create a pending item with the doubt | | An item failed | create a pending item with the real error and **move on** to the next ones | | Volume above expected | process up to the limit and create a pending item listing the excess | | An absolute prerequisite is missing | write the result saying where it stopped and exit with a non-zero code | ``` source /tmp/agent_state.sh create_todo "$(jq -nc --arg doc "$CNPJ" \ '{title:"Payment with no matching receivable",description:"No open account for the CNPJ in the amount paid.",payload:{cnpj:$doc,valor:1520.00}}')" ``` Write the skill for **both modes** when it is also useful in Claude Code, with a person on the other side: ``` if [ -n "${GITHUB_ACTIONS:-}" ]; then MODE=task; else MODE=local; fi ``` ## Inputs[​](#inputs "Direct link to Inputs") | Variable | What it brings | | ------------------- | ---------------------------------------------------------------------------------------- | | `$PARAMETERS` | the run's parameters, in JSON, already validated against `parameters_schema` | | `$REFERENCE_DATE` | the reference date (`YYYY-MM-DD`): the execution's "today" | | `$LOCALE` | the language of whoever triggered the run; write events and result in it | | `$INPUT_FILES` | one path per line, only with `accepts_input_files: true`. The files are already on disk. | | `$OUTPUT_FILES_DIR` | only with `generates_output_files: true`: everything saved here is published | | secrets | each key in `required_secrets` arrives as an environment variable (`$FINTERA_TOKEN`) | ``` source /tmp/agent_state.sh PARAMS="${PARAMETERS:-}"; [ -z "$PARAMS" ] && PARAMS='{}' REPO_URL=$(jq -r '.repo_url // empty' <<<"$PARAMS") ``` Do not invent a missing value. An empty essential parameter is an honest failure, not a reason to guess. ## Finalization: you write the file, the wrapper sends it[​](#finalization-you-write-the-file-the-wrapper-sends-it "Direct link to Finalization: you write the file, the wrapper sends it") At the end, write `./.execution-result.json` with `result` and `summary`: ``` source /tmp/agent_state.sh RESULT=$(cat <<'MD' ## Summary 12 payments settled, 2 skipped (see pending items). MD ) SUMMARY='{"processados":12,"pulados":2}' jq -n --arg result "$RESULT" --argjson summary "$SUMMARY" \ '{result:$result, summary:$summary}' > ./.execution-result.json ``` * **`result` is not a status.** It is the final Markdown that the Result tab shows, in the language of `$LOCALE`. `{"result":"success"}` makes the tab display the word "success". * **`summary` is an object of counters**, not a sentence. * **The wrapper is what sends the result to the console**, from this file. Do not call the finalization in the body and never resend the logs at the end: the Logs tab is fed gradually, and a resend overwrites it. Fatal error midway: write the `.execution-result.json` **anyway**, saying where it stopped, and exit with a non-zero code. The failure status comes from the exit code; do not try to mark the failure on your own. ## Conversational tasks[​](#conversational-tasks "Direct link to Conversational tasks") With `conversational: true`, the person can request changes after the execution finishes (the **Conversation** card in the console, or `POST /v1/executions/{id}/continue`). Each request creates a **child** execution, and the child turn receives: * the **same** parameters, reference date and environment as the previous one. The request arrives in `$ADDITIONAL_INSTRUCTIONS`; * the previous one's **agent session**: the model sees what it read, ran and produced; * the previous one's files, at the same paths: `.execution-files/input/`, `.execution-files/output/` (`$OUTPUT_FILES_DIR`) and `.execution-files/state/`. The rest of the working directory is discarded between turns. The wrapper and the body run again, from the start: the model decides, from the request and the history, what to redo. This changes how the body is written: | Rule | Why | | --------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | | Keep in `.execution-files/state/` whatever is worth reusing (script, assets, editing project) | It is the only working directory preserved, besides inputs and outputs. | | Write outputs to `$OUTPUT_FILES_DIR` as usual, overwriting the file that changes | A restored output that comes back unchanged is not published again; the changed one is published as the new turn's output. | | Make every effect on an external system idempotent | The body runs again on every turn. Without checking before creating, a turn that only adjusts a text duplicates the first turn's charge, account or email. | | Write each turn's `result` as the complete current state | Each execution's Result tab shows only its own `result`. | | Do not depend on anything outside `.execution-files/` | Installed packages, `/tmp` and the rest of the working directory do not survive. | | Avoid large intermediates | Session, inputs, outputs and state add up to at most 1 GiB. Above that the execution finishes normally, but cannot be continued. | Both executors continue conversations: `claude_code` resumes its transcript, `goose` its session (`goose run --resume`). The session belongs to the executor that saved it: if the task's tier or executor override changes between turns, the next one is refused (`409 EXECUTOR_CHANGED`) and the task has to be run again. On `goose`, the cheaper models of tiers 1 and 2 get a history that grows every turn: check that the result holds up after a few turns. ## A helper that runs inside a task[​](#a-helper-that-runs-inside-a-task "Direct link to A helper that runs inside a task") A small helper (sending a Slack message) does not need its own instrumentation: the task that calls it records the step. But when the task **orchestrates** helpers that do the bulk of the work, silent helpers leave the timeline and logs empty for almost the whole execution. A helper called inside an execution runs in the same process and sees the helpers. So it instruments its own steps: | Tab | Who feeds it | | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Timeline | the task opens and closes each phase; the helper emits its own steps' `step.started`/`step.completed`, with **counts** (the individual item goes to a file, not to an event) | | Logs | the helper records one line per tool or batch | | Pending | the helper creates its own | | Result | **only the task** writes `.execution-result.json`; the helper returns a file and a summary | Document this in a "Kadu instrumentation" section of the helper, so whoever reads it knows which tab to look in. ## Body template[​](#body-template "Direct link to Body template") ```` # Olá, Kadu Example task: it accesses no external system and asks no questions. ## Step 1 — start ```bash source /tmp/agent_state.sh emit_event "$(jq -nc '{type:"step.started",level:"info",step:"1",message:"Preparing the greeting"}')" mark_body_started PARAMS="${PARAMETERS:-}"; [ -z "$PARAMS" ] && PARAMS='{}' NOME=$(jq -r '.nome // "time"' <<<"$PARAMS") append_log "Parameters received: $PARAMS" ``` ## Step 2 — result Write a short greeting for `$NOME`, in the language of `$LOCALE`, mentioning the reference date (`$REFERENCE_DATE`). Write the result: ```bash source /tmp/agent_state.sh RESULT="## Hello, $NOME!" jq -n --arg result "$RESULT" '{result:$result, summary:{cumprimentos:1}}' > ./.execution-result.json emit_event "$(jq -nc '{type:"step.completed",level:"info",step:"2",message:"Greeting written"}')" ``` ```` --- # Task dependencies The agent runs without administrator rights: it cannot install system packages or start databases. A task that needs them **declares** them in the `task:` block, and Kadu installs them **before** the execution starts. ``` task: # ... dependencies: apt: [ffmpeg, poppler-utils] # system packages services: [postgres] # local databases ``` The repository only picks **names** from a list maintained by Kobana. For a service, the version, port, user and password come from the list, never from the repository. ## What happens in the execution[​](#what-happens-in-the-execution "Direct link to What happens in the execution") 1. The execution starts and Kadu checks each name against the list. A name outside the list, or disabled in it, fails the execution before anything is installed. 2. Packages are installed and services start on the execution's own machine, reachable only from it (`127.0.0.1`). 3. Each service reaches the skill in an environment variable (`DEPENDENCY_POSTGRES_URL` etc.), with the connection URL ready. 4. The timeline shows the "dependencies" step with the time spent, or the reason for the failure. Only then does the skill start. Installing counts toward the execution's 60 minutes: declare only what the task uses. ## Rules[​](#rules "Direct link to Rules") | Rule | Detail | | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | Names from the list only | A name outside the list shows in the sync warnings and fails the execution | | Limits | At most 30 packages and 5 services per task | | Format | Lists of text; any other key (like `npm:`) or type discards the whole block, with a warning | | Absent | `dependencies` missing from the frontmatter keeps what was stored; `dependencies: {}` clears it | | Pairs that cannot coexist | `mysql` and `mariadb` (same port), `redis` and `valkey` (same port), `mysql-client` and `mariadb-client`. Declaring both of a pair fails the execution | | SQLite | It is a file, not a service: the `sqlite3` package is enough | :::info Why a list Packages and services are installed with administrator rights, before the skill runs. The list makes sure only known software goes in, from the official Ubuntu repository or from official images pinned to an exact version. Even with the list, Kadu refuses an installation that would give the skill a way to become administrator of the machine, and the execution fails with the reason. ::: ## Available packages[​](#available-packages "Direct link to Available packages") ### PDF[​](#pdf "Direct link to PDF") | Package | What for | | --------------- | --------------------------------------------------------------------------- | | `poppler-utils` | Extract text and images, convert pages (`pdftotext`, `pdftoppm`, `pdfinfo`) | | `qpdf` | Merge, split, rotate, decrypt and repair | | `pdftk-java` | Merge, split and fill forms (`pdftk`) | | `mupdf-tools` | `mutool`: extract, render and clean | | `ghostscript` | Render, compress and convert PDF/PostScript | | `img2pdf` | Images → PDF, lossless | | `ocrmypdf` | Scanned PDF → searchable PDF (OCR) | ### OCR[​](#ocr "Direct link to OCR") | Package | What for | | ------------------- | ------------------- | | `tesseract-ocr` | OCR for images | | `tesseract-ocr-por` | Portuguese language | | `tesseract-ocr-eng` | English language | | `tesseract-ocr-spa` | Spanish language | ### Documents (DOC, DOCX, ODT, RTF, PPT)[​](#documents-doc-docx-odt-rtf-ppt "Direct link to Documents (DOC, DOCX, ODT, RTF, PPT)") | Package | What for | | --------------------------- | -------------------------------------------------------------------- | | `libreoffice-writer-nogui` | DOC/DOCX/ODT: convert and render PDF headless (`soffice --headless`) | | `libreoffice-impress-nogui` | PPT/PPTX/ODP: convert and render PDF headless | | `pandoc` | Convert between Markdown, DOCX, HTML and more | | `antiword` | DOC (Word 97-2003) → text | | `catdoc` | Old DOC, XLS and PPT → text (`catdoc`, `xls2csv`, `catppt`) | | `docx2txt` | DOCX → text | | `odt2txt` | ODT/ODS → text | | `unrtf` | RTF → text or HTML | ### Spreadsheets and CSV[​](#spreadsheets-and-csv "Direct link to Spreadsheets and CSV") | Package | What for | | ------------------------ | ----------------------------------------------------- | | `libreoffice-calc-nogui` | XLS/XLSX/ODS: convert and recalculate headless | | `gnumeric` | `ssconvert`: XLS/XLSX/ODS/CSV without LibreOffice | | `csvkit` | `csvlook`, `csvsql`, `csvjoin`, `in2csv` (XLSX → CSV) | | `miller` | `mlr`: filter, aggregate and convert CSV/TSV/JSON | | `xlsx2csv` | XLSX → CSV | | `sqlite3` | SQLite: query CSV and file databases | ### Images[​](#images "Direct link to Images") | Package | What for | | ------------------------ | ------------------------------------------------ | | `imagemagick` | Convert, resize and compose images | | `graphicsmagick` | `gm convert`/`mogrify` (ImageMagick alternative) | | `libvips-tools` | `vips`: large images with little memory | | `webp` | WebP (`cwebp`, `dwebp`) | | `optipng` | Lossless PNG optimization | | `jpegoptim` | JPEG optimization | | `pngquant` | Lossy PNG compression | | `librsvg2-bin` | SVG → PNG/PDF (`rsvg-convert`) | | `libimage-exiftool-perl` | `exiftool`: image, PDF and audio metadata | ### Audio and video[​](#audio-and-video "Direct link to Audio and video") | Package | What for | | ---------------- | -------------------------------------------------- | | `ffmpeg` | Cut, convert and assemble audio and video | | `sox` | Convert, cut, normalize and apply effects to audio | | `libsox-fmt-mp3` | MP3 in `sox` | | `lame` | Encode MP3 | | `flac` | FLAC | | `opus-tools` | Opus (`opusenc`, `opusdec`) | | `vorbis-tools` | OGG Vorbis (`oggenc`, `oggdec`) | | `mediainfo` | Audio and video metadata | ### Files and data[​](#files-and-data "Direct link to Files and data") | Package | What for | | ------------ | -------------------------------------- | | `zip` | Create ZIP | | `unzip` | Extract ZIP | | `p7zip-full` | 7z, and reading RAR and other formats | | `xmlstarlet` | Query and edit XML (NF-e, CNAB in XML) | | `html2text` | HTML → text | | `yq` | Query and convert YAML and XML | ### Services and storage[​](#services-and-storage "Direct link to Services and storage") | Package | What for | | --------- | ----------------------------------------------------------------------- | | `gh` | GitHub CLI | | `git-lfs` | Git LFS | | `rclone` | Copy and sync files with Google Drive, Dropbox, OneDrive, S3 and others | | `s3cmd` | S3 and compatible storage | ### Database clients[​](#database-clients "Direct link to Database clients") | Package | What for | | ------------------- | ------------------------------------------------------------------- | | `postgresql-client` | `psql`, `pg_dump` | | `mysql-client` | `mysql`, `mysqldump` (cannot be installed with `mariadb-client`) | | `mariadb-client` | `mariadb`, `mariadb-dump` (cannot be installed with `mysql-client`) | | `redis-tools` | `redis-cli` | | `valkey-tools` | `valkey-cli` | ### Fonts and display[​](#fonts-and-display "Direct link to Fonts and display") | Package | What for | | ------------------------ | ----------------------------------------------- | | `fonts-noto-core` | Noto fonts (Latin, accents) | | `fonts-noto-color-emoji` | Color emoji | | `fonts-liberation2` | Metric-compatible with Arial, Times and Courier | | `fonts-dejavu-core` | DejaVu fonts | | `xvfb` | Virtual display for a browser with a UI | ## Available services[​](#available-services "Direct link to Available services") | Service | Version | Variable | Value | User / password / database | | ---------- | ------------------------- | ------------------------- | -------------------------------------------------------- | ------------------------------------ | | `postgres` | PostgreSQL 18 | `DEPENDENCY_POSTGRES_URL` | `postgresql://postgres:postgres@127.0.0.1:5432/postgres` | `postgres` / `postgres` / `postgres` | | `mysql` | MySQL 8.4 (LTS) | `DEPENDENCY_MYSQL_URL` | `mysql://root:mysql@127.0.0.1:3306/app` | `root` / `mysql` / `app` | | `mariadb` | MariaDB 11.4 (LTS) | `DEPENDENCY_MARIADB_URL` | `mysql://root:mariadb@127.0.0.1:3306/app` | `root` / `mariadb` / `app` | | `redis` | Redis 7 | `DEPENDENCY_REDIS_URL` | `redis://127.0.0.1:6379` | no password | | `valkey` | Valkey 8 (Redis protocol) | `DEPENDENCY_VALKEY_URL` | `redis://127.0.0.1:6379` | no password | The database starts empty on every execution and goes away with it: it is for temporary work (building data, running an app in development mode), not for keeping anything between executions. ## What is not on the list[​](#what-is-not-on-the-list "Direct link to What is not on the list") | You need | Do this | | ---------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | | npm or pip packages, browsers | The skill installs them in its own folder, without administrator rights (`npm install -g --prefix "$HOME/.local"`, `npx`, `pip install --user`) | | Slack CLI, Google Cloud CLI (`gcloud`), Linear | Not in the Ubuntu repository. Install them in the skill's folder, or call the HTTP API with `curl` | | AWS CLI | Not in Ubuntu 24.04. For S3, use `s3cmd` or `rclone` | | Google Drive, Dropbox, OneDrive | `rclone` | | Another package or database | Ask Kobana support (below) | ## Requesting a package or service[​](#requesting-a-package-or-service "Direct link to Requesting a package or service") Contact Kobana support with the name and what the task needs it for. The package must be in the official Ubuntu 24.04 repository and must not ship programs with special permissions; the service needs an official image. Once added, it is available to the next executions, with no change to the repository. ## Common errors[​](#common-errors "Direct link to Common errors") | Symptom | Cause | Fix | | ----------------------------------------------------------- | ----------------------------------------------------------------------- | ------------------------------------------------------------ | | "Outside the allowlist" warning in the sync | Name not on the list, or disabled | Fix the name or request it | | Execution fails before starting, in the "dependencies" step | Name outside the list, a pair that cannot coexist, or a refused package | Read the reason on the timeline | | `dependencies` ignored, with a warning | Unknown key or wrong type in the block | Only `apt` and `services`, as lists of text | | Connection to the database refused | Service not declared, or the skill reads another variable | Declare it in `services` and use the `DEPENDENCY_*` variable | | Execution runs out of time | Too many packages | Declare only what the task uses | --- # Local development A Playbook's tasks run on your machine, in [Claude Code](https://docs.claude.com/en/docs/claude-code), with the same instrumentation plugin as production. Outside Kadu this plugin switches to **simulation mode** (dry-run) on its own: everything that would go to the console is just printed in the terminal. ## Prepare[​](#prepare "Direct link to Prepare") Prerequisites: [Claude Code](https://docs.claude.com/en/docs/claude-code), `jq` and `curl` on the `PATH`, and **test** credentials for the external systems. 1. **Test secrets.** Copy the versioned template and fill it in: ``` cp sandbox/.env.example sandbox/.env # sandbox/.env stays out of git set -a; source sandbox/.env; set +a ``` Do not export `GITHUB_ACTIONS`: its absence is what keeps the simulation on. 2. **Plugins**, in Claude Code. Install `kadu` (instrumentation) and your Playbook's plugins: ``` /plugin marketplace add Kadu-Kobs/kadu-kobs-core /plugin install kadu@kadu-kobs-core /plugin marketplace add / /plugin install finance@ ``` `kadu-kobs-core` is a Kobana repository. If `marketplace add` cannot find it, ask Kobana support for access. What comes after the `@` is the `name` in `marketplace.json`, not the repository name. To iterate on local code, add the marketplace by path (`/plugin marketplace add ./` at the repository root). ## Run a task[​](#run-a-task "Direct link to Run a task") Trigger it through the wrapper, as in production: ``` /kadu:agent-run-task finance:task-baixar-pagamentos ``` Or call the skill directly (`/finance:pagarme-atualizar-cadastro`). **What to watch for:** * **Simulated instrumentation:** each event, log, pending item and the result appear on stderr with the `[dry-run POST ...]` prefix and are accumulated in `$AGENT_MOCK_LOG` (default `/tmp/kadu_dryrun.ndjson`), one JSON line per call: ``` jq . /tmp/kadu_dryrun.ndjson ``` * **External calls are real.** ERP, gateway, invoice issuer: none of that goes through the simulation. Use test credentials. :::note A new skill does not show up in the session where it was written Claude Code reads the plugin catalog at the start of the session. A newly created skill answers `Unknown skill` even with the `SKILL.md` on disk. Restart the session, or follow the `SKILL.md` body by hand, which is the same content. ::: ## Test as CI[​](#test-as-ci "Direct link to Test as CI") The simulation exercises the **skill's reasoning**. It does not exercise the **plumbing** of a real execution: the runner, plugin resolution by `skill_slug`, delivery of the allowlist secrets, the instrumentation's HTTP sending and masking. Kobana's repository has a CI sandbox for this (`sandbox/ci/`), worth copying: it runs the execution workflow locally with [`act`](https://github.com/nektos/act) and points the instrumentation at an **ingestion API mock** inside the job itself. ``` ./sandbox/ci/run.sh smoke finance:task-baixar-pagamentos ./sandbox/ci/run.sh live finance:task-baixar-pagamentos ``` | | `smoke` | `live` | | ------------------ | ------------------------------------------------ | ----------------------- | | Calls the AI model | no | **yes** (spends tokens) | | Runs the skill | no (a fixed test body) | **yes** | | External effect | none | **real** | | Proves | runner, plugin, allowlist, HTTP sending, masking | the skill end to end | :::danger The mock is mandatory The `kadu` plugin chooses between simulation and real sending through the `GITHUB_ACTIONS` variable, and `act` sets it. A naive `act`, pointed at the production console, writes an execution, events and pending items **for real** from a local test. The sandbox aborts if the console URL is not `localhost`. ::: The `smoke` fails the job when a call arrives for a route the ingestion does not have, when authentication is missing, when no event arrives, and when a secret value appears in what the mock received (the masking canary). ## The cycle up to production[​](#the-cycle-up-to-production "Direct link to The cycle up to production") | # | Step | Where | Command | What it proves | | - | --------------- | ---------------------- | -------------------------------------------- | ----------------------------------------------------------- | | 1 | Write the skill | local branch | — | — | | 2 | Simulation | Claude Code | `/kadu:agent-run-task :` | the skill body | | 3 | Workflow lint | terminal | `actionlint` | the shell in the `run:` blocks | | 4 | CI smoke | terminal | `./sandbox/ci/run.sh smoke :` | runner, plugin, secrets, sending, masking | | 5 | CI live | terminal | `./sandbox/ci/run.sh live :` | the skill end to end, **with real external effects** | | 6 | Merge to `main` | GitHub | — | **code deploy** | | 7 | Sync | automatic, or **Sync** | — | task created or updated from the frontmatter | | 8 | Secrets | Settings → Secrets | — | the execution receives the `required_secrets` variables | | 9 | First run | console | **Run now** | timeline, logs, pending items and result appear as expected | Steps 2 and 4 complement each other, and neither replaces the other: the simulation exercises the skill with no network to the console; the smoke exercises the plumbing with no AI model. --- # Common errors ## Sync and tasks[​](#sync-and-tasks "Direct link to Sync and tasks") | Symptom | Cause | Solution | | ------------------------------------------------------------ | --------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | | The task did not appear in the console | no `task:` block, no `activity:`, or the sync has not run yet | Complete the frontmatter and use **Sync**; read "Tasks skipped" | | Duplicate activity, task in the new one | `activity:` name differs from the existing one | Fix the frontmatter to the existing name and ask for the duplicate to be removed | | I changed `workspace:` and the task did not change workspace | the workspace is the plugin's; `workspace:` only names it on creation | To change workspace, the skill has to move to another plugin (and becomes another task) | | I changed `model:` and the tier did not change | the value is not the slug of an active tier | Use `tier-N`; the warning appears in the sync result | | The tier changed, but the AI model did not | the task has an executor and model override | Declare `executor: inherit`, or ask for it to go back to "Inherit from Tier" | | I removed a frontmatter field and the task kept the value | missing field = "do not touch" | Declare it empty (`[]`, `null`, `""`) | | I edited the task in the console and the edit vanished | the field is declared in the frontmatter | Make the change in the frontmatter | | A deleted task does not reappear | the sync treats the deleted one as existing | Ask Kobana to restore it | | Two tasks for the same skill, one with no executions | the skill folder was renamed | Restore the folder name, or retire the old one and reconfigure the new one | | I changed the frontmatter and the console did not change | the sync has not run, or the field was discarded for a wrong type | **Sync** and check "Tasks updated" and the warnings | | `task:` flag ignored | wrong type in the YAML (`"true"` instead of `true`) | Fix the type | ## Schedules and environments[​](#schedules-and-environments "Direct link to Schedules and environments") | Symptom | Cause | Solution | | ---------------------------------------------------------------------- | ----------------------------------------------------------------- | ------------------------------------------------------------------------- | | Warning `schedules ignorado (item N …)` | malformed item (no `cron`, quoted `paused`, repeated environment) | Fix the item; schedules do not change until then | | Warning `agendamentos não aplicados (ambiente "…" não existe)` | different environment name, or environment not yet created | Copy the name from the console, or declare the environment in `kadu.yaml` | | A schedule created in the console did not go away with `schedules: []` | the sync only deletes what it created | Delete it in the console | | Schedule runs at the wrong time | missing `timezone` = UTC | Declare `timezone: America/Sao_Paulo` | | I paused in the console and the sync resumed | the item declares `paused: false` | Remove `paused` from the item, or declare `true` | | Warning `kadu.yaml: … — nada aplicado` | broken YAML, color not in `#rrggbb`, wrong key, repeated name | Fix the file; nothing changes until then | | Environment left `kadu.yaml` and is still in the console | it still has a secret or schedule, or was created in the console | Remove the secrets and schedules, or delete it in the console | | Warning `secret X declarado no kadu.yaml sem valor no cofre` | expected key not registered in the environment | Register the value in **Settings → Secrets** | | Warning `executor claude_code não roda o modelo …` | model from another provider with `cli: claude_code` | Use `claude-*` or an alias; for another provider, `cli: goose` | ## Endpoint and webhooks[​](#endpoint-and-webhooks "Direct link to Endpoint and webhooks") | Symptom | Cause | Solution | | -------------------------------------------------------- | --------------------------------------------------------------- | ---------------------------------------------------------------------------------- | | Endpoint-without-credential warning | the repository never carries a credential | Ask for the credential to be registered; the next sync turns it on | | An endpoint turned off in the console turned back on | the frontmatter declares `enabled: true` | Declare `enabled: false` | | Warning `Webhook "X" criado sem credencial…` | the repository never carries a credential | Register the credential in the console and generate the signing secret | | The receiver rejects the webhook signature | the signing secret was generated by the sync, and nobody saw it | `POST /v1/webhooks/{id}/rotate-secret` and configure the new value in the receiver | | Warning `auth "…" não trocado` | `auth:` differs from the existing webhook | Switch the type in the console (the credential changes along with it) | | A deactivated webhook becomes active again on every push | `enabled: true` declared | Remove `enabled` so that deactivation by failures takes effect | | Warning `URL recusada` | private IP, loopback or `http` | Use a public `https` URL | ## Execution[​](#execution "Direct link to Execution") | Symptom | Cause | Solution | | ------------------------------------------------------------------------------ | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | | Execution ends with `execution.body_skipped` | the body did not call `mark_body_started` | Set the sentinel at the start | | A step vanished from the timeline | event without `message`, silently rejected | Always `type` **and** `message` | | Execution runs for 60 minutes and fails | the skill asked a question | No questions: decide and record a pending item | | Result tab shows "success" | `result` used as a status | `result` is Markdown; counters go in `summary` | | Logs tab emptied at the end | the logs were resent at finalization | Do not resend logs; the wrapper finalizes | | Skill runs with the variable empty | secret in the allowlist, but with no value in the environment | Register the value; check the order of the two steps | | Parameter ignored | schema without `additionalProperties: false` | Close the schema | | Empty timeline between the opening and the end | the task orchestrates helpers that do not instrument themselves | Instrument the helpers ([The task body](https://docs.kadu.ia.br/en/en/playbook/corpo-da-tarefa.md#a-helper-that-runs-inside-a-task)) | | Logs tab unreadable, thousands of lines | `log_http_call` on every request of a tool | Summarize the traffic in `append_log` | | Configuration does not change per organization, even with a value in the vault | variable read with a default in the body and outside `required_secrets` | Add the variable to `required_secrets` | | `422 missing_secrets` on the endpoint | secret in the list with no value in the call's environment | Register the value in the environment given in `environment_id` | | `Unknown skill` in Claude Code | skill created after the session started | Restart the session | --- # Playbook structure The Playbook has a catalog at the root and one directory per plugin. Each plugin is a Kadu workspace. ``` kadu-kobs-/ ├── .claude-plugin/ │ └── marketplace.json ← catalog: plugin name → directory ├── kadu.yaml ← (optional) environments, webhooks, metadata ├── CLAUDE.md ← rules for whoever edits the Playbook with a coding agent ├── README.md ├── finance/ ← plugin = "finance" workspace │ ├── .claude-plugin/ │ │ └── plugin.json ← plugin manifest │ ├── .mcp.json ← (optional) the workspace's MCP servers │ ├── docs/ ← (optional) supporting material for the skills │ └── skills/ │ ├── task-baixar-pagamentos/ │ │ └── SKILL.md ← task (has a task: block) │ └── fintera-listar-contas-a-receber/ │ └── SKILL.md ← helper (no task: block) └── / └── ... ``` ## The catalog: `.claude-plugin/marketplace.json`[​](#the-catalog-claude-pluginmarketplacejson "Direct link to the-catalog-claude-pluginmarketplacejson") Lists the Playbook's plugins. The catalog's `name` identifies the Playbook. ``` { "name": "kadu-kobs-kobana", "description": "Kadu Playbook for the Kobana organization. One plugin per workspace.", "owner": { "name": "Kobana" }, "metadata": { "pluginRoot": "./" }, "plugins": [ { "name": "finance", "source": "./finance", "description": "Finance workspace: settlement of accounts receivable (boleto/PIX and credit card)." } ] } ``` :::warning The plugin name is the slug prefix Each plugin's `name`, in the catalog and in `plugin.json`, **must be** the prefix of the `skill_slug` of its tasks. A skill at `finance/skills/task-x/` becomes the task `finance:task-x`, and it is through `finance` that each execution finds the plugin directory in the Playbook. ::: A Playbook with a single plugin can do without the catalog, as long as the plugin is in a subdirectory with its own name and has `.claude-plugin/plugin.json`. Prefer declaring the catalog: it makes things explicit. ## `plugin.json`[​](#pluginjson "Direct link to pluginjson") ``` { "name": "finance", "version": "1.0.0", "description": "Skills of the Finance workspace: settlement of accounts receivable and the integrations they orchestrate.", "author": { "name": "Kobana" } } ``` ## `.mcp.json`[​](#mcpjson "Direct link to mcpjson") MCP servers that the workspace's skills use. Kadu loads the file along with the plugin. Credentials come in through an environment variable, which must be in the task's `required_secrets`: ``` { "mcpServers": { "kobana": { "type": "http", "url": "https://mcp.banking.kobana.com.br", "headers": { "Authorization": "Bearer ${KOBANA_MCP_TOKEN}" } } } } ``` In an execution there is nobody to complete an interactive OAuth login: use servers that accept a token in the header. If the variable is not defined, the `${...}` stays literal and the connection fails. ## Skills[​](#skills "Direct link to Skills") Each skill is a folder with a `SKILL.md`. The folder name is the skill's name and the second part of the `skill_slug`. Supporting files (scripts, templates, examples) sit next to the `SKILL.md`, and the skill references them by relative path. ``` --- name: task-ola-kadu description: Demo task — greets whoever triggered it. Does not touch any external system. user-invocable: true task: label: "Olá, Kadu" description: "I am a demo task: I show how an execution appears in the Console." workspace: Demonstração activity: Primeiros passos model: tier-3 parameters_schema: null required_secrets: [] --- # Olá, Kadu Instructions the agent follows... ``` | Key | What for | | ---------------- | ---------------------------------------------------------------------------------------------------------------------- | | `name` | same as the folder name | | `description` | one operational line about what the skill does; it is how the agent finds the skill | | `user-invocable` | `true` lets the skill be called by `/:` | | `task` | the block that turns the skill into a task ([The `task:` block](https://docs.kadu.ia.br/en/en/playbook/bloco-task.md)) | Do not confuse `description` (root, short, for the agent to find the skill) with `task.description` (introduction text in the console, which can be long). ## Integrations inside the plugin[​](#integrations-inside-the-plugin "Direct link to Integrations inside the plugin") Each workspace is **self-contained**. The helper skills that talk to an external system (`fintera-*`, `pagarme-*`, `stone-*`) live inside the plugin that uses them, with the same prefix (`finance:fintera-listar-contas-a-receber`). The only external dependency is the `kadu` plugin, which Kadu loads in every execution. ## New workspace[​](#new-workspace "Direct link to New workspace") 1. Create the plugin directory with `.claude-plugin/plugin.json` (`name` = the workspace slug) and the `skills/` folder. 2. Register the plugin in the root catalog (`.claude-plugin/marketplace.json`). 3. Write at least one skill with a `task:` block. The workspace is created in Kadu by the sync, from the tasks: a plugin with no task does not become a workspace. ## Starter Playbook[​](#starter-playbook "Direct link to Starter Playbook") Kobana can create the organization's Playbook already set up: catalog, `README.md`, `CLAUDE.md` and a `demo` plugin with the demo task `demo:task-ola-kadu` (Demonstração workspace, Primeiros passos activity), with no external system. It is the best starting point: replace the demo with your own tasks. --- # The `kadu.yaml` What belongs to the whole organization, rather than to a task, lives in an optional `kadu.yaml` at the repository root: **environments**, **webhooks** and the `metadata` of **workspaces and activities**. What belongs to a task (schedule, endpoint, executor) stays in the [`task:` block](https://docs.kadu.ia.br/en/en/playbook/bloco-task.md). ## Rules that apply to all sections[​](#rules-that-apply-to-all-sections "Direct link to Rules that apply to all sections") * **Ownership.** The sync **creates and updates** what is declared and only **deletes what it created itself**. What was created in the console and appears in the file is updated, never deleted. * **A missing section touches nothing.** An empty list (`[]`) removes only what the repository created. * **The whole file is validated.** Broken YAML, a field with the wrong type, an unknown key (`colour:`, `bearer_token:`), a webhook event outside the list or a repeated item make the sync **apply nothing** from the file, with a warning. An unknown top-level section is only a warning, and the rest applies. All-or-nothing is on purpose: applying the good half would delete the item with the typo. ## Environments[​](#environments "Direct link to Environments") ``` environments: - label: Produção color: "#16a34a" # #rrggbb; missing = gray (#6b7280) on creation branch: main # missing = main on creation secrets: # only the KEYS — a value never goes into the repository - KOBANA_ACCESS_TOKEN - key: FINTERA_TOKEN notes: token da entidade 34684 - label: Sandbox color: "#f59e0b" ``` | Situation | What the sync does | | ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | | Declared environment that does not exist | creates it, marked as the repository's | | Declared environment that already exists (same name) | updates `color` and `branch`, if declared. The one created in the console is adopted for updating, **never** marked as the repository's | | Key in `secrets` with no value in the environment | **warning**: the sync never creates a secret | | Key in `secrets` with `notes` | overwrites the secret's note | | **Repository's** environment that left the list | deletes it, unless it still has a secret or schedule (it stays, with a warning) | | **Console's** environment that left the list | does not touch it | The name is the key: renaming an environment in the file creates another one. The schedules that point to it must change in the frontmatter in the same commit. To rename an environment that already has secrets, rename it in the console and then in the file. ## Webhooks[​](#webhooks "Direct link to Webhooks") ``` webhooks: - label: Falhas para o PagerDuty # the key: matched by name url: https://events.exemplo.com/kadu events: [execution.failed, organization.cost_limit_reached] workspace: finance # scope plugin; null = whole organization; missing = do not touch auth: header # basic | bearer | header; missing = bearer on creation header_key: X-Api-Key # the non-secret part (for basic: basic_user) enabled: true # missing = do not touch (on creation: active) emails: [ops@exemplo.com] # notified when the webhook is deactivated by failures metadata: { dono: sre } ``` Accepted events: `execution.created`, `execution.queued`, `execution.started`, `execution.completed`, `execution.failed`, `execution.cancelled`, `todo.created`, `todo.resolved`, `organization.cost_limit_reached`, `organization.entitlement_required` and the `.db.created|updated|deleted` events of `task`, `activity`, `workspace`, `execution` and `memory`. **A credential never goes into the file.** `bearer_token`, the basic password or the header value in the YAML are unknown keys and invalidate the whole file. | Situation | What the sync does | | ----------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | | Declared webhook that does not exist | creates it, marked as the repository's, **without a credential** and with a signing secret that nobody has seen, and warns | | Existing webhook (same name, any origin) | updates `url`, `events`, `emails` (order does not matter), scope, `basic_user`/`header_key` and `metadata` | | `auth` different from the stored one | **does not switch** (it would delete the credential) and warns; switch in the console | | `enabled: true` on a webhook deactivated by failures | reactivates it and resets the failure counter, on every sync, while declared | | URL rejected (private IP, `http`), nonexistent workspace, two webhooks with the same name | skips **that** webhook, with a warning, without deleting it | | **Repository's** webhook outside the list | deletes it | | **Console's** webhook outside the list | does not touch it | After creating it, two steps remain outside the repository: register the credential in **Settings → Webhooks** and generate the signing secret with `POST /v1/webhooks/{id}/rotate-secret`, configuring the new value in the receiver. Until then, the delivery goes out unauthenticated and with a signature that the receiver cannot verify. ## Workspaces and activities[​](#workspaces-and-activities "Direct link to Workspaces and activities") ``` workspaces: - plugin: finance # the plugin = the workspace metadata: { centro_de_custo: "42" } activities: - label: Baixa de Recebimentos # exact name within the workspace metadata: { sla_horas: 4 } ``` Only the `metadata`, which appears in the v1 API and in the webhook payload, replaced whole when declared. The file **does not create** a workspace or activity (they come from the tasks), so a plugin or name that does not exist becomes a warning. ## Order of application[​](#order-of-application "Direct link to Order of application") Tasks → environments → schedules → workspace and activity metadata → webhooks → removal of the repository's environments. See [Sync](https://docs.kadu.ia.br/en/en/playbook/sincronizacao.md). --- # Secrets in the Playbook **No secret value lives in the Playbook.** The frontmatter declares only the **names**, in `required_secrets`, and this list is an allowlist: only what is in it reaches that execution. ## The path of a secret in production[​](#the-path-of-a-secret-in-production "Direct link to The path of a secret in production") 1. The value stays in Kadu's vault, registered per environment in **Settings → Secrets**. 2. At the start of the execution, Kadu delivers **only** the secrets listed in the task's `required_secrets`, as environment variables, and registers them for masking. 3. The skill body reads `$KOBANA_ACCESS_TOKEN`, `$FINTERA_TOKEN` directly from the environment. 4. Before sending any event, log, pending item or result to the console, the instrumentation replaces each secret's value with a mask. Masking covers every variable in `required_secrets`, whatever its name, and catches the value as is, JSON-escaped (including JSON inside JSON) and, for a multi-line value, line by line. It ignores values shorter than 6 characters and does not recognize a transformed value (base64, URL-encoded). Masking is the last line of defense; the first is not producing the leak. Never echo, log or include the value of a secret in the result, in events or in pending items. ## Provisioning a new secret: order matters[​](#provisioning-a-new-secret-order-matters "Direct link to Provisioning a new secret: order matters") 1. Add the name to the task's `required_secrets` and merge. 2. Check that the sync ran and listed the task under "Tasks updated". Before that, the old list remains in effect. 3. Register the value in the environment, before the next run time if the task has `schedules`. In the reverse order, the execution calls the external system with an empty variable and fails midway, not at the start. ## Configuration is a secret too[​](#configuration-is-a-secret-too "Direct link to Configuration is a secret too") The allowlist is Kadu's **only** channel for environment variables. What is not a secret but needs to be able to change per organization also goes in it: a Linear project id, an ERP entity id (`REPORT_LINEAR_PROJECT_ID`, `FINTERA_ENTITY_ID`). This catches whoever writes `${VAR:-default}` in the body: **a variable read with a default and outside the allowlist can never be overridden by Kadu.** The skill works, the default always applies, and nobody notices until they need to change the value without a deploy. To check, cross the variables the body reads with `required_secrets`. Whatever is left must be exported by the body itself (working directory, for example), never configuration. Adding an optional variable to the list is safe: with no value in the vault, it just does not arrive. The exception is the [run endpoint](https://docs.kadu.ia.br/en/en/api/overview/run-task-endpoint.md), which rejects the call with `422 missing_secrets` when a secret in the list has no value in the environment. ## Inventory in `kadu.yaml`[​](#inventory-in-kaduyaml "Direct link to inventory-in-kaduyaml") Each environment in [`kadu.yaml`](https://docs.kadu.ia.br/en/en/playbook/kadu-yaml.md) can list the keys it should have. This **delivers nothing** to any task: it lets the sync **warn** when an expected key has no value in that environment. | | `required_secrets` (in the task) | `secrets` (in the `kadu.yaml` environment) | | ---------- | ---------------------------------- | ------------------------------------------ | | What it is | the execution's allowlist | the environment's expected inventory | | Effect | the variable reaches the execution | warning on the sync if a value is missing | ## In local development[​](#in-local-development "Direct link to In local development") There is no vault: test values come from a `.env` outside git (for example `sandbox/.env`, from a `sandbox/.env.example` versioned with only the names). Outside Kadu the instrumentation runs in simulation mode, so no secret is sent anywhere. See [Local development](https://docs.kadu.ia.br/en/en/playbook/desenvolvimento-local.md). --- # From merge to console Two paths read the same repository, at different paces: | | Skill code | Catalog and tasks | | ----------------- | --------------------------------------------- | ---------------------------------------------------- | | Who reads | each execution | the organization's sync | | When | every execution clones the default branch | on each push, hourly, or through the **Sync** button | | What it consumes | the whole `SKILL.md` and the files next to it | only the **frontmatter** and `kadu.yaml` | | Effect of a merge | immediate, on the next run | on the next sync | A merge that changes a skill's **body** takes effect on the next run. A merge that changes the **frontmatter** takes effect on the next sync. ## Merging to `main` is a deploy[​](#merging-to-main-is-a-deploy "Direct link to merging-to-main-is-a-deploy") Each execution clones the default branch, with `--depth 1`, with no pinned tag or commit. So: * what runs is the **HEAD of the default branch** at the moment of the run; * a merge reaches production on the **next execution of any task** in the organization, with no build, release or deploy window; * a broken `main` breaks **all** the organization's tasks immediately; * rollback is `git revert` and push. The console has no "go back to the previous version". Work on a branch, validate with the [CI sandbox](https://docs.kadu.ia.br/en/en/playbook/desenvolvimento-local.md) and treat the merge with the care of a deploy. ## When the sync runs[​](#when-the-sync-runs "Direct link to When the sync runs") | Trigger | When | | -------------------------- | ------------------------------------------------------------------------------------------------------------------ | | Push to the default branch | notice from the GitHub App (or from the repository webhook, without the App). A push to another branch is ignored. | | Hourly check | compares the remote HEAD with the last commit read and syncs if it changed. Covers a missed notice. | | Manual | **Sync** in **Settings → Deployment** (or `POST /v1/repository/sync`). | In practice, the push to `main` already syncs. The button is for seeing the result right away. ## What the sync does, in order[​](#what-the-sync-does-in-order "Direct link to What the sync does, in order") 1. **Stores the catalog**: the frontmatter of every `SKILL.md` with a `task:` block. A skill without the block is left out. 2. **Ensures the structure**: one workspace per plugin and the declared activities. It only creates: it never renames or deletes. 3. **Updates the existing tasks**, matched by `skill_slug` (`:`). 4. **Creates the missing tasks** for skills with `task:` that have no task in the organization. 5. **Applies the environments** from `kadu.yaml` (creates and updates). 6. **Applies each task's schedules** (`task.schedules`). 7. **Applies the `metadata`** of workspaces and activities and the `kadu.yaml` **webhooks**. 8. **Deletes the repository's environments** that left `kadu.yaml`, last, so that a schedule that moved environments has already left the old one. You do not need to register the task by hand: the merge creates the task. ## The Playbook is the source of truth[​](#the-playbook-is-the-source-of-truth "Direct link to The Playbook is the source of truth") In step 3, every **declared** field in the `task:` block that differs from Kadu is overwritten. The comparison of `parameters_schema` and `metadata` ignores key order, so reformatting the YAML does not generate an update. | Frontmatter field | What the sync does to the existing task | | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | | `label`, `description` | overwrites | | `model` | switches the tier (price, executor and model). A value that is not a tier: not applied, with a warning | | `parameters_schema` | overwrites; `null` clears | | `parameters_sensitive`, `accepts_input_files`, `generates_output_files` | overwrites | | `systems`, `features`, `required_secrets` | overwrites the whole list | | `activity` | moves the task to the activity with that name; creates the activity if it does not exist | | `workspace` | ignored: the task stays in the plugin's workspace | | `endpoint` | reconciles; never changes the authentication type or turns on without a credential | | `schedules` | creates, updates and deletes the repository's schedules | | `executor` | stores the executor and model override; `inherit` clears | | `metadata` | overwrites the whole object | Rules that follow from this: * **A missing field means "do not touch".** Deleting a frontmatter line does not return the field to the default. To clear, declare it empty: `required_secrets: []`, `features: []`, `parameters_schema: null`, `description: ""`. * **An edit made in the console to a declared field is undone** on the next push or the next hour. Change it in the frontmatter. * **A schedule and environment edited in the console revert to what was declared** on the next push. A pause made in the console is the exception, as long as the item does not declare `paused`. * **A deleted task does not come back.** The sync counts the deleted task as existing: it neither recreates nor updates it. And the reverse: **removing the `task:` block does not delete the task**, it only stops updating it. To retire a task, ask Kobana to remove it and remove the block. * **Renaming the skill folder creates another task**, without history, without endpoint credential and with only the declared schedules, and leaves the old one orphaned. To change the name in the console, change `label:`. * **Moving the skill to another plugin** changes the slug prefix: it also creates another task. ## Where to see the result[​](#where-to-see-the-result "Direct link to Where to see the result") The sync through the button shows what changed: * workspaces and activities created; * **tasks updated**, with the changed fields (`Baixar pagamentos (label, model)`); * tasks created and tasks skipped, with the reason; * **Repository configuration**: environments, schedules, webhooks and metadata applied (`Agendamento Baixar @ Produção criado (0 9 * * 1-5)`); * **Warnings**: invalid tier, endpoint without credential, invalid `kadu.yaml`, secret declared without a value, schedule not applied. A sync triggered by a push or by the hourly check shows the result to nobody. If a change did not appear in the console, sync through the button and read the warnings. | Task skipped with the reason | Cause | | ------------------------------------------- | ---------------------------------------------------------------------------- | | ` (sem task.activity no frontmatter)` | `activity:` missing: without an activity there is nowhere to create the task | | ` (nenhum tier de preço ativo)` | `model` missing or invalid and no active tier registered | ## Sequence for a new task[​](#sequence-for-a-new-task "Direct link to Sequence for a new task") 1. Write the skill with the `task:` block (with `activity:` and `model: tier-N`). 2. Validate locally (`/kadu:agent-run-task :`) and in the CI sandbox ([Local development](https://docs.kadu.ia.br/en/en/playbook/desenvolvimento-local.md)). 3. Merge to `main`: the code is already in effect, and the sync **creates the task**. 4. Sync through the button and check the task under "Tasks created", with no warnings. 5. Register the secret values (and ask for the endpoint credential, if any). If the task has `schedules`, do this before the first run time: the schedule is in effect from step 3. 6. Run it once and check the timeline, logs, pending items and result. Step 6 is not a formality: it is where an event without `message`, a result with a status instead of Markdown and a question that blocks the execution show up. ## Sequence for changing a task[​](#sequence-for-changing-a-task "Direct link to Sequence for changing a task") 1. Change the frontmatter, never the folder name. 2. Merge to `main`. 3. Check under "Tasks updated" that the field went in. If it does not appear: the field has the wrong type (silently discarded), it is `workspace:` (ignored) or the value was already the same. --- # When the task needs its own workflow Kadu's default execution runs the skill on a runner with Node, `jq` and Python. A task that depends on a tool the runner does not bring, on a known egress IP or on its own runner needs a **GitHub Actions workflow in the repository itself**. Kobana's case is the `security` workspace: `security-scan.yml` installs the pentest suite, schedules the weekly and monthly scan and runs on a runner with a fixed egress IP. The template to copy is the same repository's `wh-run-skill.yml`: Playbook checkout, Claude Code installation, a stub of the helpers in `/tmp/agent_state.sh`, `claude -p "/:" --plugin-dir` and `set +e` around the pipeline. ## Two execution paths[​](#two-execution-paths "Direct link to Two execution paths") The task now has two paths, and what differs between them appears nowhere unless it is written down: | | Run by Kadu | Own workflow | | --------------- | -------------------------------------- | ---------------------------------------------------------------- | | Runner | Kadu's, without the tools | the workflow's, with the tools installed | | Variables | only what is in `required_secrets` | only what the step's `env:` block maps from `secrets.*`/`vars.*` | | Instrumentation | the real console | the stub, which only echoes in the run log | | Output files | `$OUTPUT_FILES_DIR`, published by Kadu | the workflow's own `upload-artifact` | Two consequences: * **Every configuration variable goes in both places**: in `required_secrets`, for Kadu, and in the workflow's `env:`, for the schedule. Forgetting one side makes the configuration apply on one path and fall back to the default on the other. * **A missing tool becomes a pending item, not an error.** A run from the console arrives without the tools: the skill records what was missing and moves on. Whoever needs the full execution is the workflow. ## A workflow that generates traffic is born closed[​](#a-workflow-that-generates-traffic-is-born-closed "Direct link to A workflow that generates traffic is born closed") A workflow that generates traffic against third parties or against production is born **fail-closed**: without the authorization variable (`vars.SECURITY_SCAN_AUTORIZADO=true`, in Kobana's case), the job ends green with a `::notice` and does nothing. A cron that is red every week because of missing configuration becomes noise nobody reads. --- # Getting started This walkthrough takes an organization from first sign-in to its first execution. ## 1. Sign in to the Agent Console[​](#1-sign-in-to-the-agent-console "Direct link to 1. Sign in to the Agent Console") Go to `https://app.kadu.ia.br` and sign in with your **Kobana Account**. If you belong to more than one organization, choose which one to open; you can switch later from the user menu, under **Switch organization**. ![Console sign-in screen](/en/assets/images/login-04cb705d10e348b71c39ceb5182f914b.png) ## 2. Connect the Playbook[​](#2-connect-the-playbook "Direct link to 2. Connect the Playbook") Kadu's tasks live in your organization's Kadu Playbook, a git repository. While the organization has none, the console shows the **Connect your Playbook** card: 1. Click **Install GitHub App** and choose, on GitHub, the repository that holds the skills. The App only asks for read access to the content of the repositories you choose. 2. GitHub returns to the console, which confirms that the installation is yours and links the repository to the organization. If the installation gives access to more than one repository, the console asks you to choose which one holds the Playbook (those with the Playbook catalog, `.claude-plugin/marketplace.json`, are marked). 3. Kadu reads the repository and creates the workspaces, activities and tasks declared in it. From then on, each push to the default branch updates the console automatically. If you do not have permission to install the App on the GitHub account, the installation becomes a request to the account owner. As soon as they approve it, the repository is connected. Do not have a Playbook yet? The [Kadu Playbook](https://docs.kadu.ia.br/en/en/playbook.md) section explains how to set one up. Kobana can also create a starter Playbook with a demo task for your organization. ## 3. Check the plan[​](#3-check-the-plan "Direct link to 3. Check the plan") Running a task requires Kadu to be in the organization's plan with Kobana. Without it, the console shows the **Subscribe** bar and the run buttons are blocked. Everything else (registration, reading, settings) remains available. See [Plan and costs](https://docs.kadu.ia.br/en/en/conceitos/plano-e-custos.md). ## 4. Create an environment and register the secrets[​](#4-create-an-environment-and-register-the-secrets "Direct link to 4. Create an environment and register the secrets") Each execution runs in an **environment** (for example, Production and Sandbox), and the environment is where the secrets live: the tokens for the systems the tasks use. 1. In **Settings → Environments**, create the environment (or check the ones the repository already declared). 2. In **Settings → Secrets**, register the values. The screen marks as **Pending** every secret that a task requires and that has no value yet. See [Environments and secrets](https://docs.kadu.ia.br/en/en/conceitos/ambientes-e-secrets.md). ## 5. Run your first task[​](#5-run-your-first-task "Direct link to 5. Run your first task") ![Tasks of the Contas a receber activity](/en/assets/images/inicio-tarefas-2d54234a9fbc2445c7044f2c830e6ce1.png) 1. In the top bar, choose the **environment**, the **workspace**, the **activity** and the **task**. 2. Click **Run now**, check the reference date and the parameters, and confirm. 3. Kadu starts working within 3 minutes. Follow the timeline, logs and pending items live on the execution screen. See [Run a task](https://docs.kadu.ia.br/en/en/console/executar-tarefa.md) and [Follow an execution](https://docs.kadu.ia.br/en/en/console/acompanhar-execucao.md). ## Next steps[​](#next-steps "Direct link to Next steps") * Schedule the task to run on its own: [Schedules](https://docs.kadu.ia.br/en/en/configuracoes/agendamentos.md). * Receive the events in your system: [Webhooks](https://docs.kadu.ia.br/en/en/configuracoes/webhooks.md). * Integrate through the API: [API v1](https://docs.kadu.ia.br/en/en/api/overview/introduction.md). --- # Kadu **Kadu** is Kobana's AI agent that runs your organization's recurring tasks: settling received payments, reconciling statements, checking invoices, and opening pending items for whatever needs a person. Each task is a **skill** written in natural language and stored in a git repository owned by the organization. Kadu runs the skill, talks to the systems it asks for, and returns a report. You follow everything in the **Agent Console** (`app.kadu.ia.br`): trigger a task, watch the agent work in real time, resolve the pending items it left behind, and check how much each execution cost. ## What is in this documentation[​](#what-is-in-this-documentation "Direct link to What is in this documentation") | Section | For whom | What you will find | | ------------------------------------------------------------------ | -------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | | [Documentation](https://docs.kadu.ia.br/en/en/primeiros-passos.md) | people who use the Agent Console | the concepts (organization, workspace, task, execution), how to run and follow a task, and the organization settings | | [Kadu Playbook](https://docs.kadu.ia.br/en/en/playbook.md) | people who write the tasks | how to set up the organization's Playbook, write a skill that becomes a task, test it locally and publish it | | [API](https://docs.kadu.ia.br/en/en/api/overview/introduction.md) | people who integrate another system with Kadu | the v1 API (authentication, scopes, conventions and the reference for each endpoint), the task run endpoint and webhooks | | [MCP](https://docs.kadu.ia.br/en/en/mcp/overview/introduction.md) | people who use Kadu from an AI client (Claude, ChatGPT…) | the remote MCP server: authentication, transport, compatible clients and the reference for each tool | | [CLI](https://docs.kadu.ia.br/en/en/cli/overview/introduction.md) | people who operate Kadu from the terminal or a script | `kobana`, the command-line tool for every Kobana product: installation, authentication, usage and the commands for each resource | ## How Kadu works[​](#how-kadu-works "Direct link to How Kadu works") 1. The organization keeps its skills in a **git repository**. Each push to it updates the console's task catalog. 2. A task is triggered from the console, by a **schedule**, through the [API](https://docs.kadu.ia.br/en/en/api/overview/introduction.md) or by an HTTP call from another system ([run endpoint](https://docs.kadu.ia.br/en/en/api/overview/run-task-endpoint.md)). 3. The **execution** runs the skill in an isolated environment, with only the secrets the task declares, and records every step in the console. 4. At the end, the execution delivers a **result** in Markdown, the files it generated and the **pending items** left for a person. Start with [Getting started](https://docs.kadu.ia.br/en/en/primeiros-passos.md). ---