# Changelog

## 6 November 2026: The attention queue of a member

- **Case queue:** `GET /v2/case-queue?memberId=` (`caseQueue.retrieve`, scope `cases:read`) returns what a member has to look at, in three lists: `waitingForPerson` (the agent handed the case to a person and nobody took it, assigned to the member or without an owner, with `waitColor` green, yellow or red by the wait thresholds of the pipeline), `waitingForReply` (cases the member escalated, in a stage with `attentionRole` `escalation`) and `answered` (cases back in a stage with `attentionRole` `answer` that still need a reply to the customer). `waitMinutes` counts only the business hours of the pipeline, and each case appears once.
- **Stages:** `config.attentionRole` (`escalation`, `answer` or `null`) on `POST` and `PATCH /v2/pipelines/{id}/stages`. When a case enters an `answer` stage, its owner gets a notification.

## 6 November 2026: The API and the MCP do what the dashboard lets the role do

- **Breaking, scopes:** what the dashboard keeps for admins and the owner no longer hangs from `cases:write`, `contacts:write` and `contacts:presence`. It has scopes of its own, and a key acts with at most the role of whoever issued it:
  - `cases:delete`: `DELETE /v2/cases/{id}`, `POST /v2/cases/{id}/archive`, `POST /v2/cases/{id}/unarchive` and `POST /v2/cases/{id}/messages/{messageId}/redact`.
  - `cases:reorganize`: `POST /v2/cases/move-batch`, and `POST /v2/cases/{id}/move` when it changes the pipeline (a `pipelineId`, or a `stageId` of another pipeline). Moving between stages of the same pipeline stays in `cases:write`.
  - `contacts:erase`: `DELETE /v2/contacts/{id}`.
  - `opt-outs:write`: `PUT` and `DELETE /v2/contacts/{contactId}/opt-outs/{channel}`.
  - `contact-fields:write`: `POST /v2/contact-fields` and `PATCH`, `DELETE /v2/contact-fields/{id}`.
  - `presence:configure`: `GET` and `PUT /v2/presence/settings`. `contacts:presence` keeps only `POST /v2/presence/signals`.
- **Existing keys and connections:** the ones issued by an admin or the owner that had the old scope got the new ones, so their integrations keep working. The ones issued by a member did not: a member cannot hold them, as in the dashboard.
- **Assigning:** `POST /v2/cases/{id}/assign` records who assigned (the person who issued the key, or the person who connected through the MCP) and applies the rule of the dashboard: a member only assigns to people of their own team, and `403 forbidden` otherwise. The MCP tool `create_lead` with `assign_to` follows the same rule.
- **Scopes:** `conversations:route` (`POST /v1/external/conversations/{id}/derivar`) now needs a key issued by an admin or the owner, like `cases:reorganize`.
- **MCP:** `move_deal_to_pipeline` also needs the `cases:reorganize` permission on the connection. The tools of the CRM check the role of the person on every call: `move_deal_to_pipeline` is for admins and the owner. A tool the role cannot use is not listed.

## 5 November 2026: Escalate a case to your technical team in Linear

- **Linear:** `PUT`, `GET`, `PATCH` and `DELETE /v2/integrations/linear` (`linear.connect`, `linear.retrieve`, `linear.update`, `linear.disconnect`) connect the Linear of the organization with its own API key, choose the team and project that receive the issues and save the signing secret of the Linear webhook. New scopes `linear:read` and `linear:write`, both for admins.
- **Cases:** `POST /v2/cases/{id}/escalate-to-team` (`cases.escalateToTeam`) creates an issue in Linear with the contact, the pipeline, the last messages and the link to the case, and never a second one while the first is open. `GET /v2/cases/{id}/team-escalations` lists the escalations with their issue, status and answer.
- **Stages:** a case that enters a stage with `config.attentionRole` `escalation` is escalated by itself when Linear is connected. When the team completes the issue, the case moves to the `answer` stage of the pipeline, the answer is left as an internal note and the owner of the case is notified (once). If Linear does not answer, the escalation stays pending and Lelemon retries. See [Escalate a case to your technical team](/guides/pipelines#escalate-a-case-to-your-technical-team).

## 5 November 2026: Which cases a member sees, by API

- **Members:** `PATCH /v2/members/{id}/case-scope` (`members.updateCaseScope`) sets what a member sees: `caseScope` is `all`, `unassigned_and_own`, `assigned`, or `null` to follow the default of the organization. Same rule as the Team screen, capped by the role of whoever issued the key: it never touches someone with a higher role, and the owner and admins always see all. It needs `members:manage` and it is recorded in the audit log of members (`member.scope_changed`, with `scopeBefore` and `scopeAfter`).
- **Members:** `members.list` returns `caseScope` and `effectiveCaseScope` for each member. The MCP connector of Lelemon only works for people who see all cases; when it cannot connect someone, the consent screen and the token endpoint now say why (`access_denied`).

## 5 November 2026: A stage creates a task when a case does not move

- **Stages:** `config.noProgress` on `POST` and `PATCH /v2/pipelines/{id}/stages`. When a case stays in the stage for `minutes` (counted inside the business hours of the pipeline, when it has them), Lelemon creates a reminder of type `call` for the assignee of the case, or for `fallbackAssigneeId` when the case has no assignee, with your `title` and `description`, and can add a `tag` to the contact. One task per pass of the case through the stage. If the case leaves the stage or closes, the task closes by itself. See [Settings of a stage](/guides/pipelines).

## 5 November 2026: Triggers without a message

- **Triggers:** `templateId` is optional. A trigger with only `onEvent` opens or moves the case of the contact when the event arrives and sends nothing. `POST /v2/contact-events` answers `202` with `sent` empty, `caseActions` and `reason` set to `null`. A trigger with neither `templateId` nor `onEvent` is rejected with `400`, and `variables` is now optional when there is no template. See [Message on events](/guides/events-and-triggers).

## 4 November 2026: Organization settings and company profile

- **Organization:** `GET`, `PATCH /v2/organization`, `GET`, `PATCH /v2/organization/settings` and `GET`, `PATCH /v2/organization/company-profile`. They read and change the business hours, the inactivity closing, presence, branding, the company profile and the name, with the same validation as the dashboard.
- **Scopes:** `organization:read`, `settings:write` and `organization:write`. A key acts with at most the role of whoever issued it, so only a key issued by an owner can use `organization:write`.

## 4 November 2026: Teams by API

- **New, teams:** `teams.list`, `teams.retrieve`, `teams.create`, `teams.update`, `teams.delete` and `teams.merge`, plus `teams.members.set`, `teams.members.remove` and `teams.pipelines.set` to assign members and pipelines. They need the new `teams:read` and `teams:write` scopes. `teams:write` needs a key issued by an admin or the owner.

## 3 November 2026: Keys are managed only from the dashboard

- **Breaking, keys:** `GET`, `POST /v2/api-keys` and `PATCH`, `DELETE /v2/api-keys/{id}` and `POST /v2/api-keys/{id}/rotate` are removed, with `apiKeys.*` in the SDK and the MCP. They answer `404`. Create, rotate and revoke keys from the dashboard.
- **Breaking, scopes:** `api-keys:write` no longer exists. Keys that carried it lose it.

## 3 November 2026: Case closed event

- **Events:** `case.closed` fires once when a case changes from open to closed. Its data has `case`, `reason`, `status`, `closedBy` and `closedAt`. `closedBy` is `system`, `stage`, `agent`, `api` or `user`. It covers the inactivity sweeps, closing stages, `end_conversation`, `POST /v2/cases/{id}/close` and the closes of the dashboard. See [React when a case closes](/guides/events-and-triggers).

## 3 November 2026: Scheduled tasks for agents

- **Scheduled tasks:** `POST /v2/agents/{id}/schedules`, `GET /v2/schedules`, `GET`, `PATCH` and `DELETE /v2/schedules/{id}`. On each date of a cron, in the timezone you send, the agent gets the goal as an unattended run and writes first by email to a contact, with a file from one of its granted actions if it needs one. Ids are `sch_…`. They need the new scopes `schedules:read` and `schedules:write`. See [Scheduled tasks](/guides/scheduled-tasks).
- **`email_contact`:** a new tool you grant to the agent. It writes to the contact of the task and takes up to three attachments from HTTP actions already granted to that agent: `.csv`, `.pdf`, `.xlsx` or `.txt`, up to 5 MB each.
- **Runs:** `Run` has `schedule`, the id of the task that started it, or `null`. `GET /v2/runs` takes `scheduleId`.
- **Events:** `schedule.fired`, `schedule.skipped` and `schedule.paused`.
- **Errors:** `invalid_cron`, `invalid_timezone` and `schedule_limit_reached`.
- **Fire now:** `POST /v2/schedules/{id}/fire` starts one run right now to try a task. It does not move `nextRunAt`, it needs `schedules:write` and an `Idempotency-Key`, and it sends a real email.
- **Email:** a first email that names an agent in `onReply.agentId` reuses an open case of the contact only if that case is attended by the same agent. Before, it reused the contact's open case whoever attended it, so the answer went to the other agent.

## 2 November 2026: Runs no longer show cost

- **Breaking, `Run`:** `costUsd` and `limits.maxCostUsd` are removed from the run object and from the `run.*` event payloads. Token cost is internal; `rounds`, `limits.maxRounds` and `limits.maxMinutes` remain.
- **Breaking, `POST /v2/agents/{id}/runs`:** `limits.maxCostUsd` is no longer accepted. Sending it answers `400 unknown_field`. To cap a run, use `limits.maxRounds` and `limits.maxMinutes`.

## 2 November 2026: Claude as your team's assistant

- **Knowledge search:** `POST /v2/knowledge-collections/search` (`knowledgeCollections.search`) returns the passages of your documents that answer a question, with the collection and document of each one. It is the search the agents use. It needs `knowledge:read` and changes nothing.
- **Context of a team question:** `GET /v2/team-questions/{id}/context` (`teamQuestions.context`) returns the question with the last lines of the conversation and the data of the case. Every sensitive value comes covered. It needs `team-questions:read`, `conversations:read` and `cases:read`.
- **Sensitive values covered:** `GET /v2/team-questions` and `GET /v2/team-questions/{id}` (and the answer route's response) now cover every sensitive value of the case in `question`, `context` and `answer`, as the dashboard does. The value is replaced by `[dato oculto]`.
- **MCP:** both routes are tools of the [MCP server](/tools/mcp), marked read only. See [Use Claude as your team's assistant](/guides/team-assistant).

## 2 November 2026: Usage and budgets count conversations

Usage and budgets now measure conversations, the unit Lelemon bills: a window per case of up to 10 agent turns or 24 hours, whichever comes first. The next turn opens another conversation. The model does not change the count. Token cost is internal and no response shows it.

- **Breaking, budgets:** `monthlyLimitUsd` is now `monthlyLimitConversations` (an integer from 1) in `POST /v2/budgets` and `PATCH /v2/budgets/{id}`, and in the `Budget` object. `spentUsd` is now `usedConversations`. No budget created before this change is kept: the dollar limits have no equivalent in conversations. Create them again in conversations.
- **Breaking, `402 budget_exceeded`:** `budget.limitUsd` and `budget.spentUsd` are now `budget.limitConversations` and `budget.usedConversations`. The error answers only when the message would open a new conversation. A case whose conversation is already open keeps answering.
- **Breaking, events:** `budget.threshold_reached` and `budget.exceeded` carry `monthlyLimitConversations` and `usedConversations` instead of `monthlyLimitUsd` and `spentUsd`.
- **Usage:** `GET /v2/usage` adds `conversations` to `totals` and to the groups by day, agent and subaccount. It counts from 2 November 2026 on; earlier days show zero. The groups by model do not carry it.

## 1 November 2026: Test mode removed

- **Keys:** only `lel_live_` keys exist. `lel_test_` keys stop working, and `POST /v2/api-keys` no longer takes `livemode`.
- **`livemode`:** removed from every object, event and webhook payload.
- **Test helpers:** `POST /v2/test-helpers/whatsapp/inbound` and the other `/v2/test-helpers` routes are gone. Use a number you connected.
- **Errors:** `test_mode_limit` and `test_mode_only` no longer exist.

## 1 November 2026: Erasure reaches stored responses and opt-outs hold

- **Idempotency:** the response stored for an `Idempotency-Key` is erased when the person in it is suppressed. A retry with that key answers `409 idempotency_key_reused`.
- **Queues:** the WhatsApp history and pipeline job queues are purged when a contact is erased.
- **Opt-outs survive erasure:** an opt-out keeps a keyed hash of the phone and the email, so a new contact with the same number or address is still opted out.
- **One rule for WhatsApp:** an opt-out blocks every send to the number, including replies from the team and the test send of a template (`422 contact_opted_out`). The dashboard disables the send button and says why.

## 1 November 2026: Safer inputs

- **Nesting limit:** a body or a query string with objects or arrays nested more than 32 levels deep answers `400 invalid_request` with `param` set to `body` or `query`. It used to answer `500 internal_error`.
- **Test send and opt-outs:** `POST /v2/whatsapp/templates/{id}/test` checks opt-outs by phone number. A number that belongs to a contact who opted out of `whatsapp` answers `422 contact_opted_out` and nothing is sent. `dryRun=true` answers the same.
- **Case attachments:** `POST /v2/cases/{id}/attachments` reads the type from the whole content and compares it with the declared `Content-Type`, as `/v2/files` does. A file that is not of its declared type, or that carries HTML or SVG with scripts, answers `400 invalid_request` with `param: "file"`.

## 1 November 2026: Limits that hold under load

- **Webhook endpoints:** an organization can have up to 16. The next `POST /v2/webhook-endpoints` answers `422 webhook_endpoint_limit`. See [Receive events](/webhooks/receive).
- **Limits per organization:** `POST /v2/actions/{id}/test` (10 per minute), `POST /v2/data-connectors/{id}/check` (20) and `POST /v2/mcp-servers/{id}/discover` (20) are counted for the whole organization across all servers, and answer `429 rate_limited` with `Retry-After`. See [Rate limits](/basics/rate-limits).
- **Pipelines and stages:** deleting a pipeline or a stage while cases are being opened or moved into it can no longer leave a case in a deleted stage or pipeline, and a stage can no longer point to a stage that was just deleted.

## 1 November 2026: One MCP server, at api.lelemon.cl/mcp

- **Remote MCP with OAuth:** `https://api.lelemon.cl/mcp` exposes the tools of the v2 API. Paste the URL in Claude, Cursor or ChatGPT, sign in and choose the organization, the permissions and real or test data. There is no API key to copy. See [MCP server](/tools/mcp).
- **Permissions you grant:** a connection lists only the tools its scopes cover, and each call goes through the same rules as a call with an API key. Creating API keys, new actions, approvals, revealing values, webhooks, connectors and the recipient allowlist are never granted through the MCP.
- **Connections in the dashboard:** Developers lists each connection with its client, permissions and last use, and removes its access at once.
- **Security:** PKCE `S256`, tokens bound to the server with `resource` (RFC 8707), refresh tokens that rotate, and no token forwarded to another service. Client registration is rate limited, the consent screen shows the host the client returns to, and permissions that change data are confirmed apart. A member can only grant the reads the dashboard lets a member see.
- **The CRM tools stay:** `find_contact`, `send_message` and the rest keep their names and work on deals.
- **`@lelemon-ai/mcp`:** it stays for environments without a browser (CI, scripts, servers) and only needs `LELEMON_API_KEY`.

## 1 November 2026: Pipeline settings and stage configuration

- **Pipeline settings:** `POST /v2/pipelines` and `PATCH /v2/pipelines/{id}` take `settings`: `autoClose`, `businessHours`, `waitThresholds`, `askFor`, `timezone`, `stageOnTakeControl` and `stageOnHandoff`. Every pipeline returns them, `null` when not set. The dashboard shows and changes the same settings. See [Pipeline settings](/guides/pipelines#pipeline-settings).
- **Stages that close:** a stage takes `closes`, `closingOutcome` and `statusType`. A case that arrives at a stage that closes ends and gets the outcome of the stage.
- **Next stage and transitions:** `nextStageId` and `transitions` on a stage, with targets in the same pipeline.
- **Stage config:** `config` takes `agentMode`, `followUp`, `maxDurationSeconds`, `minDurationSeconds` and `delayMinutes`. Webhooks, sources and the other keys of the panel are not part of it, and a key that is not documented answers `400 unknown_field`.
- **WhatsApp number by stage:** `whatsappPhoneNumberId` on `PATCH /v2/pipelines/{id}/stages/{stageId}` starts the cases of a number in that stage. A number feeds one pipeline only (`409 conflict`).

## 1 November 2026: Operate cases from the API

- **Tags and fields:** `PATCH /v2/cases/{id}` takes `tags` (replaces the list of the contact) and writes `fields` of any source, not only `system`. `Case` adds `tags`, `archivedAt` and `muted`.
- **Reveal:** `POST /v2/cases/{id}/fields/{key}/reveal` returns a sensitive field. It needs the new scope `cases:reveal`, audits the key that revealed it in the same trail the dashboard writes to, never stores the value and answers `422 field_not_sensitive` for any other field. In the MCP it is in the hidden `keys` group.
- **Archive:** `POST /v2/cases/{id}/archive` and `/unarchive`, and `GET /v2/cases?archived=`.
- **Move:** `POST /v2/cases/move-batch` moves up to 100 cases and says why each of the others did not move. `POST /v2/cases/{id}/move` takes `pipelineId` as an alternative to `stageId`.
- **Mute and escalate:** `POST /v2/cases/{id}/mute`, `/unmute` and `/escalate`. Escalating fires `case.control_changed` with `escalated: true`.
- **Reminders:** `/v2/cases/{id}/reminders` and `/v2/reminders/{id}`, with `complete` and `dismiss`. Ids are `rem_…`.
- **Notes:** `Note` adds `pinned`, and `PATCH /v2/cases/{id}/notes/{noteId}` takes `pinned`.
- **History of a person:** the cases are `GET /v2/cases?contactId=…` and the messages of each one `GET /v2/cases/{id}/messages`. New: `GET /v2/contacts/{id}/events` lists the events reported for the contact. It needs `triggers:read`, like `GET /v2/contact-events/{id}`.

## 1 November 2026: Contact fields, contact search and companies

- **Contact fields:** `/v2/contact-fields` defines the data every contact can hold (`text`, `textarea`, `email`, `phone`, `url`, `number`, `date`, `boolean` or `select`). `POST`, `PATCH` and `PUT /v2/contacts/by-external-ref/{externalRef}` take `fields`, every value is checked against its type, and contacts answer `fields`. The contact page of the dashboard shows and edits them and the Contacts list filters by them.
- **Search contacts:** `GET /v2/contacts` takes `q` (name, email, phone, `externalRef` and contact fields, with `match` saying what matched), `pipelineId`, `hasOpenCases=true`, `order` (`activity` or `name`), and `fieldKey` with `fieldValue`. It is the search of the Contacts screen, and adds `caseCount`, `openCaseCount` and `lastActivityAt` to each contact.
- **Companies:** `GET /v2/companies` and `GET /v2/companies/{id}` with `contacts:read`. `POST /v2/companies`, `PATCH /v2/companies/{id}` and `DELETE /v2/companies/{id}` need the new scope `companies:write`, which asks for the same role as the dashboard (owner or admin). A `rut` already used by a live company answers `409 conflict` with `param: "rut"`. Deleting a company keeps its contacts and clears their `companyId`.
- **Company of a contact:** `Contact` adds `companyId`. `POST /v2/contacts`, `PATCH /v2/contacts/{id}`, `PUT /v2/contacts/by-external-ref/{externalRef}`, the `contact` of `POST /v2/cases` and the `contact` of `POST /v2/contact-events` take it (`null` unlinks, except in cases and events) with `contacts:write`, and a company that does not exist is a `400` with `param: "companyId"`.
- **Account in the contact page:** a contact with an `externalRef` shows its account in your active data connector, live, with the connector's permissions and masking.

## 1 November 2026: Scheduling tools for the agent, delete a budget

- **System tools:** `check_availability`, `schedule_meeting` and `cancel_meeting` can be granted with `PUT /v2/agents/{id}/tools/{toolId}`. They book on the default agenda, so an agent no longer needs actions of your own to schedule.
- **Action names:** `POST /v2/actions` answers `409` when the name is the name of a Lelemon tool (`cancel_meeting`, `send_email`...). The agent ran the Lelemon tool instead of your action. If an action with such a name already exists, the agent is offered and runs the Lelemon tool.
- **Data connector tools:** a connector you create, check or delete shows up in the tools of an agent right away. A copilot could answer `Unknown tool` for up to five minutes after a turn had loaded the tools of the organization. The change reaches every API and worker process.
- **Budgets:** `DELETE /v2/budgets/{id}` answers `204`. The dashboard has the same action.
- **Subaccounts:** `fromTemplates.agents` takes ids from `GET /v2/agent-templates`.

## 31 October 2026: Examples in every request body, how a run ends and a typed MCP

- **Unknown fields:** a body field the route does not document answers `400 unknown_field`, and `param` names the first one. It was `invalid_request`.
- **Idempotency:** once a request starts running, its final response is stored whether it is `2xx`, `4xx` or `5xx`, and a retry with the same `Idempotency-Key` gets the same response without repeating the effect. A request rejected before it runs, such as a body that fails validation, leaves the key free. A replayed create does not repeat the secret. See [Idempotency](/basics/idempotency).
- **MCP servers shared with subaccounts:** a platform connects a server with `sharedWithSubaccounts: true`, or turns it on with `PATCH /v2/mcp-servers/{id}`. A subaccount lists it with `sharedByPlatform: true` and grants its tools, but cannot change it. See [Platforms](/guides/platforms#share-an-mcp-server-with-all-your-subaccounts).
- **Usage:** `GET /v2/usage` adds `cases` to `totals`, and to the groups by day, agent and subaccount: the cases where the agent wrote at least one reply.
- **Connector in the contract:** the `webhooks` section of `openapi.json` declares the four requests your connector serves, with the schemas of the format as components (`ConnectorManifest`, `ConnectorAccountSearch`, `ConnectorAccount`, `ConnectorView`).
- **Runs:** the `completed` status is gone from the contract. Nothing produced it; a run that finishes is `completed_unverified`.
- **Contract:** `AgentModel` and its `fallback` in agent answers now describe the v2 model (`model`, `thinking`, `effort`...). The published contract carried a v1 schema under that name.
- **Cancel:** `POST /v2/runs/{id}/cancel` on a run that is `waiting_for_answer` answered `500 internal_error`. It now ends `canceled`, with `question` back to `null`, and fires `run.canceled`. The same applies to the v1 routes.
- **Runs:** the [runs guide](/guides/runs#how-a-run-ends) says how a run ends. The agent ends it by calling its finish action after using at least one tool. An agent that only answers in text ends `stopped` with `stopReason: "no_conclusion"` and its answer in `result.partial`. `rounds`, `cost` and `time` are limits; `no_conclusion` and `loop` are not. Every `stopReason` is now described in the contract and in the `run.*` events.
- **Examples:** every JSON request body in the contract has a valid `example`. Bodies that accept several shapes (`agents.create`, `cases.create`, `caseMessages.create`, `whatsapp.messages.create`, `email.messages.create` and others) have one named example per shape in `examples`, and each branch of a `oneOf` or `anyOf` has its own `example`. Postman and Insomnia import them instead of `"<string>"`.
- **MCP:** each tool whose success answer has a body declares an `outputSchema` and returns the answer as `structuredContent`. Calls now wait 50 seconds, under the 60 seconds an MCP client waits by default, so the client receives the `timeout` error with the `idempotencyKey` to retry. New error code `invalid_response`.

## 31 October 2026: Case metrics and action variables in v2

- **`GET /v2/case-metrics`:** one row per case that changed in a range of up to 31 days, with when it came in, was handed off, was taken and was closed, who closed it (`agent`, `human` or `system`), whether the agent resolved it alone and the seconds with the agent and with a person. It needs `cases:read`, paginates with `cursor` and reads one subaccount with `Lelemon-Subaccount`. It replaces `GET /v1/external/metrics/conversations`, and the dashboard shows the same numbers on the home page, to owners and admins.
- **`variables` on actions:** `POST /v2/actions` and `PATCH /v2/actions/{id}` take `variables`, the fields Lelemon fills when it calls your endpoint (`case.id`, `contact.phone` or a field of the case), and every read of an action returns them. A variable that cannot be saved answers `400 invalid_request` with the `param` that failed, `400 action_variable_in_query` when it would go in a `GET` action, and `400 action_variable_needs_authorization` when `contact.phone` or `contact.whatsapp_id` lack the authorization of the destination. `authorization.dataSent` names the data in English.
- **Moving from v1:** the conversation metrics are no longer listed as missing in v2.

## 31 October 2026: Agendas, meetings and team availability

- **Scheduling:** `GET /v2/scheduling/agendas` and `GET /v2/scheduling/agendas/{id}`, `GET /v2/scheduling/availability?agendaId=…` and the meetings: `POST /v2/scheduling/meetings` (idempotent, with `dryRun`), `GET /v2/scheduling/meetings`, `GET /v2/scheduling/meetings/{id}`, `POST …/cancel` and `POST …/reschedule`. Ids are `agd_…` and `mtg_…`. They need `scheduling:read` and `scheduling:write`, and `scheduling:read` now also reads the meetings, which carry the attendee. Meetings booked by an agent or in the dashboard show up too, and the dashboard lists the ones booked by API in Mi Negocio. See [Schedule meetings](/guides/scheduling).
- **Events:** `meeting.scheduled`, `meeting.rescheduled` and `meeting.canceled`, for every meeting whoever creates it.
- **Team availability:** `GET` and `PUT /v2/members/{id}/availability`, with `status` `available` or `away` and `availableForAssignment`. Reading needs `members:read`; setting needs the new scope `members:write`, which only sets availability. The v1 scopes `miembros:read` and `miembros:write` keep working on the v1 routes only.
- **Errors:** new codes `slot_not_available` and `meeting_not_active`.
- **Moving from v1:** scheduling and team availability leave the list of what v2 does not have yet.

## 31 October 2026: Runs, jobs an agent does on its own

- **Runs:** `POST /v2/agents/{id}/runs` gives an agent a `goal` that it works on outside your request, with `limits` for rounds, spending and minutes. `GET /v2/runs`, `GET /v2/runs/{id}`, `POST /v2/runs/{id}/cancel` and `POST /v2/runs/{id}/answer` list, read, cancel and answer it. Scopes `runs:write` and `runs:read`. `POST` takes `Idempotency-Key` and `dryRun`.
- **No signature:** the run works for the `actor` your key declares, recorded as declared by the key and sent signed by Lelemon to every action and MCP server. Without `actor` it works as the key. There is no shared secret and no `pedidoPor`. A role above the key answers `403 actor_role_above_key`.
- **Statuses:** `running`, `waiting_for_answer`, `completed_unverified` (the agent says it finished and nobody checked), `stopped` (with `stopReason`), `failed` and `canceled`.
- **Events:** `run.started`, `run.waiting_for_answer`, `run.completed`, `run.stopped`, `run.failed` and `run.canceled`.
- **Errors:** new codes `run_finished` and `run_not_waiting`.
- **Dashboard:** the runs of an agent show under it, in **Activity**, and a run that asks something is answered there.
- **Migration:** runs leave the list of what v2 does not have yet.

## 30 October 2026: Talk to a copilot, ordered stages and truthful statuses

- **Copilot cases:** `POST /v2/cases` accepts the `agentId` of a copilot (`audience: "internal"`) with a `pipelineId` or `stageId` for the case to live in. The copilot answers the messages written with `author: "contact"` and the case shows `agentId` of the copilot, fires the same events and shows in the dashboard. A copilot cannot open email cases or work a stage, and activating it does not ask for a stage: `readiness` only checks the `api` channel.
- **Stages:** `GET /v2/pipelines/{id}/stages` lists the stages in funnel order, from `position` 0 up, and its pages follow that order.
- **MCP servers:** a successful discovery, including the one `PATCH /v2/mcp-servers/{id}` runs after new credentials, puts the server back to `connected`.
- **Data connectors:** `422 data_connector_invalid_response` names the failing paths and the rule each one breaks in its `detail`. The check endpoint reads the manifest only.
- **Docs:** the key can be created in the dashboard or by API, and `GET /v2/actions/authorization` is listed as available in v2.

## 30 October 2026: One naming, a stricter contract and SDKs for external developers

- **Names:** every single-object read is `retrieve` (`budgets.retrieve`, `usage.retrieve`, `recipientAllowlist.retrieve`, `email.domains.retrieve`). WhatsApp routes are `whatsapp.connections.*` and `whatsapp.phoneNumbers.*` (with `commerceSettings.retrieve` and `update`), email domains are `email.domains.*`, `knowledgeCollections.documents.delete` and `subaccounts.apiKeys.create`. The paths did not change.
- **Fields:** the key of `POST /v2/subaccounts/{id}/api-keys` is in `secret`, like every other secret the API returns once. The id of an agent tool is `id`, not `toolId`.
- **Contract:** every operation declares the scopes it needs (`x-required-scopes`, also as the scopes of its security requirement), the `Idempotency-Key` on every `POST` that uses it and the `Lelemon-Subaccount` header. `dryRun` answers declare `dryRun`. Recipients, case messages, interactive messages, agent bodies and approval conditions are `oneOf` or `anyOf` unions, and a body with required content is required.
- **Errors in the headers:** a `Lelemon-Subaccount` that is not a `sub_…` id answers `400` with `param: Lelemon-Subaccount`, and `404` only when the id is well formed and is not one of your subaccounts. A missing required `Idempotency-Key` answers `400` before the subaccount is looked up, and a method that a fixed route does not have (for example `PATCH /v2/actions/authorization`) answers `405`. Every operation documents the `400`, `404` and `409` its headers can cause.
- **Webhooks:** `Lelemon-Delivery-Id` is the `whd_` id of the attempt.
- **`agents.activate`** accepts `dryRun`.
- **Errors:** every code has a concrete cause, and the generic ones have a `fix`.
- **TypeScript SDK and MCP server:** the SDK ships ESM and CommonJS, runs on Web Crypto, throws one class per error family and takes one `options` object (`query`, `idempotencyKey`, `subaccount`, `signal`). The MCP server filters its tools by the scopes of the key and returns the idempotency key it used. Neither is on npm yet.

## 30 October 2026: Rules for approvals, and automated decisions with any mode

- **Rules:** `approval.mode` accepts `rules`, an ordered list. The first rule whose `when` applies decides, with the effect `allow`, `require_approval` (its own approvers and deadlines) or `reject` (a hard cap that never asks). Policies saved before keep working unchanged. A platform floor still applies on top of the rules.
- **Roles include the roles above them:** an approval assigned to `role: admin` is seen and decided by owners too.
- **Automated decisions (Ley 21.719, art. 8 bis):** `automatedDecision` no longer requires `mode: always`. Calls that nobody approved, or that a rule rejected, are recorded and read with `GET /v2/automated-decisions` and `GET /v2/automated-decisions/{id}`. `POST /v2/automated-decisions/{id}/request-review` and `POST /v2/approvals/{id}/request-review` store what the person said (`reviewReason`). New event `automated_decision.review_requested`, and `reasonCode` can be `policy_rule` (with `ruleIndex`) or `policy_not_required`.
- **`approve` with `dryRun`** answers `status: "preview"` and the decision it would record, instead of `status: "executed"` with no decision.
- **Erasing a contact** cancels its pending approvals (event `approval.canceled`) and discards its open team questions, which before stayed `pending` with their arguments emptied.
- **`actions.test`** on a `noPersist` action no longer leaves the input or the answer in the request log or in the idempotency store.
- **What the agent is told:** a call that waits says `pending_approval`, `approved_running` or `rejected_by_policy`, and the action did not run. The state shows in the `output` of the step in `GET /v2/cases/{id}/turns`.
- **Errors:** `agent_not_ready` and `unprocessable` carry a `fix`; new code `review_not_available`.

## 30 October 2026: Files for messages, campaigns and templates

- **`POST /v2/files`:** upload a file once (multipart, with `purpose` `message_media`, `campaign_media` or `template_header`) and get a `fil_…` id. `GET /v2/files`, `GET /v2/files/{id}` and `DELETE /v2/files/{id}` list, read and delete. New scopes `files:read` and `files:write`. `?dryRun=true` validates the file and stores nothing.
- **Use it as `fileId`:** in `document`, `components.headerImage`, `components.headerVideo` and the cards of a media carousel, in messages and campaigns, and as `exampleFileId` in the `header` and the carousel cards of a template. Lelemon uploads the file to WhatsApp by media id (messages) or resumable upload (template samples). A URL keeps working.
- **Limits per purpose** are the ones in Meta's media table, and the type is read from the content. A wrong type or content is `400 invalid_request` with `param: "file"`, a file too big is `413 payload_too_large`, and a file used in the wrong place names the field. Deleting a file that a campaign that has not finished uses answers `409 resource_in_use`.
- **Person data:** a `message_media` file uploaded with `contactId` can only be sent to that contact and is erased with them. The files of a case (`/v2/cases/{id}/attachments`) are a different thing and stay as they were.

## 30 October 2026: Approvals and team questions

- **Approvals:** `PUT /v2/agents/{id}/tools/{toolId}` accepts an `approval` policy. A call that meets it becomes an approval that waits; only `POST /v2/approvals/{id}/approve` runs it. `GET /v2/approvals`, `reject` and `request-review` complete the resource, with the scopes `approvals:read` and `approvals:write`.
- **Who decides:** a member, or an API key that the policy names. The key that proposed the action never decides it. Every decision needs a reason.
- **Limits:** a condition on the arguments, an accumulated limit per contact and period, an expiry and an escalation. Events `approval.requested`, `approval.decided`, `approval.expired`, `approval.escalated` and `approval.review_requested`.
- **Team questions:** the `ask_team` system tool, `GET /v2/team-questions` and `POST /v2/team-questions/{id}/answer`, with the scopes `team-questions:read` and `team-questions:write`. Events `team_question.created` and `team_question.answered`. The dashboard shows both queues and edits the policy of each tool. A platform can set an approval floor on a shared action with `PUT /v2/actions/{id}/approval-floor`. See [Approve agent actions](/guides/approvals).

## 30 October 2026: Every WhatsApp format available in Chile

- **New interactive types:** `cta_url`, `flow`, `location_request`, `contact_request`, `call` and `carousel` in `interactive` of `POST /v2/cases/{id}/messages`. `buttons` and `cta_url` take `headerMedia`. Limits and fields are in the [WhatsApp guide](/guides/whatsapp).
- **What the person sends back:** a shared location, the answer of a Flow and a phone shared for a `contact_request` arrive as a message of the case (`location`, `flowResponse`, `contact`) and as `message.received` with `type` `location`, `flow_response` or `contact`. The event carries no personal data.
- **Failures:** a message with `status: "failed"` has `error: { code, message }`.
- **Catalog and commerce:** `catalogId` on phone numbers, and `GET` and `PATCH /v2/whatsapp/phone-numbers/{id}/commerce-settings` (`isCartEnabled`, `isCatalogVisible`). A message that uses a catalog answers `422 catalog_not_connected` when the number has no catalog or the id differs.
- **Templates:** image and video headers (`components.headerImage`, `components.headerVideo`), a variable at the end of a link button, `catalog`, `mpm` and `flow` buttons, and `carousel` (media and product cards). The sends take `components`; campaigns and `POST /v2/whatsapp/templates/{id}/test` too.
- **System tools:** `send_carousel`, `send_cta_url`, `send_flow` and `request_location` can be granted with `PUT /v2/agents/{id}/tools/{toolId}`.
- **Not available in Chile:** `order_details`, `order_status` and payment inside WhatsApp (Kapso Pix is only for Brazil), and the address message (India only). Payment stays an external link: use `cta_url`.

## 30 October 2026: A contract with nothing pending

- **Nothing deprecated:** the `provider` field and the AWS model ids are gone, strict bodies apply to every key. An unknown field answers `400 unknown_field`, a model that is not in `GET /v2/models` answers `422 model_not_supported`.
- **`dryRun` on everything that sends, charges or configures:** `whatsapp.messages.create`, `email.messages.create`, `caseMessages.create`, `contactEvents.create`, `whatsapp.templates.test`, `apiKeys.rotate`, `campaigns.create` and `update`, `actions.create` and `update`, `triggers.create` and `update`. It runs the same checks as the real call and answers `200` with `dryRun: true`, the object as it would be created and an id that is not saved. A route that sends messages or costs money must accept it: the contract tests fail otherwise.
- **`estimatedCost` of a campaign is never `null`:** it is the list price from the WhatsApp Business Platform rate card published by Meta (https://developers.facebook.com/docs/whatsapp/pricing), by the category of the template and the country of each phone, with `effectiveFrom`. Utility and authentication volume tiers are not applied, so the real charge can be lower.
- **Errors that say what to do:** `agent_not_ready` names what the agent lacks, `thinking_not_supported` replaces `model_not_supported` when a model cannot turn thinking off and points at `thinking`, and a trigger or campaign variable written as `{{contact.name}}` is a `400` that shows the right form, `contact.name`.
- **Data connectors:** `http://localhost` is accepted while developing, under the same rule as webhook endpoints.
- **OpenAPI 3.1 with `webhooks`:** `GET /v2/openapi.json` is OpenAPI 3.1. Each of the 27 event types is a webhook with its payload (the `Event` envelope plus the `data` of that type) and the `webhook-signature` scheme (`Lelemon-Signature`). `nullable` is now a JSON Schema type list (`["string", "null"]`), so the TypeScript SDK types of fields that can be `null` include `null`.

## 30 October 2026: Copilots and MCP from the API

- **Run identity of an MCP server:** `GET`, `POST` and `DELETE /v2/mcp-servers/{id}/run-identity` turn on, rotate and read the signed context. The secret comes once. `?dryRun=true` checks it. The `org` and `srv` claims of the JWT are public ids (`org_…`, `mcp_…`), not UUIDs. The TypeScript SDK adds `verifyMcpAssertion`.
- **Copilots:** `POST /v2/agents` takes `audience: "internal"` with the new scope `agents:internal`. You manage and use it like any agent, and `GET /v2/agents/{id}/tools` lists the `dct_` tools of your data connector. A turn that read data of the connector gets no tool that writes or calls another system. The assistant of the owner stays out of the API.
- **Connector documents:** `POST /v2/data-connectors/format/validate` checks a manifest, card, search answer or view page and names the failing paths.
- **MCP server status:** `GET /v2/mcp-servers/{id}` shows `error` and `lastError` when the last tool call failed, not only the last discovery. A 401 or 403 is told apart by its status, and `mcp_server.failed` fires on every move to rejected credentials.
- **Effects:** revealing a value and getting the link of an account action are `write`, not `read`, because they return personal data and write an audit row.
- **Budget:** `402 budget_exceeded` carries `budget` (`id`, `scope`, `subjectId`, `limitUsd`, `spentUsd`, `resetsAt`) and `Retry-After`. Limits under one cent no longer read `US$0.00`.
- **Presence:** `GET` and `PUT /v2/presence/settings` turn `presenceEnabled` on or off.
- **Actions:** `Action` returns the `authorization` that is in force, or `null`.
- **Errors:** a problem in a nested body names the whole path in `param`, a missing field says it is missing, a `fix` with `set` names the field, and a subaccount key with bad scopes names `scopes` or `authorName`.
- **Cases and campaigns:** `POST /v2/cases` takes `contact` to find or create the person by `externalRef` or phone. `schedule.at` accepts `"now"`.
- **`dryRun`:** also on rotating the signing secret of webhook endpoints, actions and connectors, and on launching and resuming a campaign.
- **Usage:** `groupBy=subaccount` counts the platform itself as the group with `subaccountId: null`.
- **Scopes:** `miembros:read` and `miembros:write` are the availability of one person; `members:read` lists the team. Their descriptions say so.
- **Docs:** the SDK page no longer says that the MCP server does not exist.

## 30 October 2026: Retries, events and tools

- **Idempotency of tool calls:** the `Idempotency-Key` that Lelemon sends to an action or an MCP server is now one per operation (turn, tool and arguments). If the model retries the same call, your service receives the same key and can answer once. Before, each retry came with another key.
- **Agents created through the API:** a contact who writes to the WhatsApp number of an agent that you put in a pipeline stage with `agentId` gets an answer. Before, the turn failed with `Agent not found`. A stage now names its agent only through `agentId`.
- **Events of a subaccount:** every event of a subaccount (`message.*`, `case.*`, `budget.*` and the rest) reaches the endpoints with `scope: customers` of its platform, with `customerId`. Before, only some did.
- **System tools:** `send_buttons`, `send_list` and `send_products` can be granted with `PUT /v2/agents/{id}/tools/{toolId}`.
- **`POST /v2/actions/{id}/test`:** signs the request like a turn.
- **`GET /v2/cases/{id}/turns`:** system tools show their public name, such as `save_case_data`.
- **`case.escalated`:** `contact.externalRef` is included.
- **`budget.exceeded`:** fires when a budget with enforcement `stop` runs out, not only when the next message is refused.

## 30 October 2026: Public model ids

- **`llmConfig.model`:** takes a public id from `GET /v2/models`, such as `anthropic/claude-sonnet-4.6`, `deepseek/deepseek-v3.2` or `amazon/nova-2-lite`. It is the same in `llmConfig.fallback`. `provider` is no longer needed: it comes from the id.
- **`GET /v2/models`:** returns `id`, `name`, `capabilities` (`thinkingCanBeOff` and `contextWindow`) and `deprecated`. The `provider` field is gone and `id` is no longer an AWS id.
- **Responses:** agents, `GET /v2/usage`, `GET /v2/cases/{id}/turns` and the `case.turn_completed` event return the public id. `llmConfig` no longer has `provider`.
- **Only public ids:** the `provider` field and the AWS ids (`us.anthropic.claude-sonnet-4-6`, `global.anthropic.claude-sonnet-5-5`) are not accepted. See [Versioning](/basics/versioning#model-ids).

## 30 October 2026: docs

- **New guides:** [Track usage and cap spending](/guides/usage-and-budgets), [Show who is online](/guides/presence) and [Keep tool data out of storage](/guides/no-persist).
- **Objects you can open:** the guides show the fields of Case, Contact, Budget and more, read from the contract.
- **Diagrams:** the main flows are drawn step by step. Each page says what you will do and what you need.
- **Errors:** every error page has the cause and how to fix it.

## 30 October 2026: Tools inside subaccounts

- **Platform key with `Lelemon-Subaccount`:** `POST /v2/actions` and `POST /v2/mcp-servers` create the tool inside the subaccount, checked against the sites of your key. Before, they answered `403 action_origin_not_allowed`.
- **Subaccount keys:** accept `actions:write` and `allowedActionOrigins`, a subset of the sites of the platform key. A rejected request names the parameter.
- **Shared tools:** `sharedWithSubaccounts` on an action lets the agents of your subaccounts use it. Calls carry `Lelemon-Subaccount-Id`. See [Run Lelemon for your customers](/guides/platforms).

## 30 October 2026: Hand off to a person

- **System tools:** `PUT /v2/agents/{id}/tools/transfer_to_human` and `.../save_case_data` let an agent hand a case to a person and write case fields. `GET /v2/agents/{id}/tools` lists them with `kind: system` and their `config`.
- **Handoff:** the stage, the business hours and the farewell are set on the grant. The farewell is written by the tool. A reply that promises a handoff on a case that was not handed over is corrected once and, if it persists, the case is handed over. See [Actions and MCP in v2](/guides/actions-and-mcp#system-tools-hand-off-to-a-person-and-write-case-fields).

## 29 October 2026: API v2

Version 2 of the API is available at `https://api.lelemon.cl/v2`. Its API version for webhooks is `2026-10-29`.

- **New resources:** subaccounts, agents and agent versions, actions, MCP servers, skills, knowledge collections, pipelines with stages, fields and handoff rules, contacts, cases with messages, notes and attachments, WhatsApp connections, numbers, templates and messages, email domains and messages, contact events, triggers, campaigns, opt-outs, the recipient allowlist, webhook endpoints, events and API keys.
- **Conventions:** prefixed ids, cursor lists, RFC 9457 errors with stable codes, `Idempotency-Key` on every `POST`, and a `Request-Id` on every response.
- **Platforms:** subaccounts with the `Lelemon-Subaccount` header, and webhook endpoints with `scope: customers`.
- **Email:** cases can run over email, with verified sender domains and threaded replies.
- **Webhooks:** signed deliveries with `Lelemon-Signature`, retries for up to 72 hours and a catalog of events.
- **Contract:** `GET /v2/openapi.json` is public and generated from the code.
- **TypeScript SDK:** generated from the contract. Not published yet.
- **Docs:** this site, with `llms.txt`, `llms-full.txt` and a Markdown version of every page.

## 29 October 2026: Built for agents

- **`fix` on errors:** when a code has a known remedy, the error carries `fix` with an `action` (`set`, `remove`, `retry`, `call` or `stop`). See [Errors](/basics/errors).
- **`?dryRun=true`:** validates and answers what would happen without saving or sending anything, and does not use the `Idempotency-Key`. `PUT /v2/recipient-allowlist` accepts it first. A route that does not accept it answers `400`.
- **`x-lelemon-effect`:** every operation in the contract declares `read`, `write`, `destructive` or `money`.
- **MCP server for the API:** generated from the contract, with annotations taken from the effect. It does not offer approvals, API keys or erasure by default. See [Actions and MCP in v2](/guides/actions-and-mcp).
- **Strict bodies:** unknown body fields are rejected with `400 unknown_field`, and `param` names the field. `metadata` is not affected, and neither are responses or events. See [Versioning](/basics/versioning#strict-bodies).
- **Request size:** a body over 1 MB answers `413 payload_too_large`.
- **Docs:** v1 guides moved from `/guias/` to `/legacy/guides/`; the old addresses redirect.

## 30 October 2026: the context of the turn in actions and MCP servers

- **Actions:** every call carries `Lelemon-Case-Id`, `Lelemon-Contact-Id`, `Lelemon-Contact-External-Ref`, `Lelemon-Subaccount-Id`, the actor and an `Idempotency-Key`, signed in `Lelemon-Signature`. Verify it with `verifyRequest` of the SDK. The `Lelemon-` prefix is now reserved. See [Actions and MCP in v2](/guides/actions-and-mcp).
- **Secret:** `GET /v2/actions/{id}/signing-secret` and `POST /v2/actions/signing-secret/rotate`.
- **Actor:** `POST /v2/cases/{id}/messages` accepts `actor` with `author: contact`.
- **MCP:** the context travels in the claims of the run identity and in `_meta`, also outside runs and when an approval resumes a call.

## 30 October 2026: events that open cases, and cadences

- **Triggers:** `onEvent` opens or moves the case when the event arrives, and `cadence` repeats the template up to 5 times until the contact replies, opts out, or the case changes stage or closes. See [Message on events](/guides/events-and-triggers).
- **Contact events:** the response carries `caseActions`, and two new reasons: `cadence_active` and `outside_window`.
- **Webhooks:** new event `trigger.attempted`.

## 30 October 2026: erasure

- **Erase a contact:** `DELETE /v2/contacts/{id}` now erases the contact, its cases, messages, files and logs, and answers `202` with a suppression. Before, it only hid the contact. Use `dryRun=true` to preview, `closeOpenCases=true` to include open cases and `legalHold=true` to record a request you must keep.
- **Follow the job:** `GET /v2/suppressions/{id}`.
- **Redact a message:** `POST /v2/cases/{id}/messages/{messageId}/redact`.
- **Events:** `contact.suppressed` and `message.redacted`, with ids only.

## 30 October 2026: interactive WhatsApp

- **Buttons, lists and products:** `POST /v2/cases/{id}/messages` takes `interactive` with `type` `buttons`, `list`, `product`, `product_list` or `catalog`. See [Send buttons, lists and products](/guides/inbox#send-buttons-lists-and-products).
- **Template buttons:** `POST /v2/whatsapp/templates` takes `buttons` of type `quick_reply` and `url`. They were ignored before; now a template that cannot be built with them answers `400 invalid_request` with `param: buttons`.
- **What the person sends back:** a tap on a button or a list row arrives as a message with `reply: { id, title }`, and a cart from the WhatsApp catalog as a message with `order`. `message.received` carries `type`, `reply` and `order`.
- **New error:** `422 interactive_unsupported_channel`, when the case is not on WhatsApp.
- **Agents:** the tools `send_buttons`, `send_list` and `send_products`.

## 2 November 2026: v1 deprecated

v1 (`/v1/external`) keeps working. Its responses carry `Deprecation` and a `Link` to [Moving from v1](/guides/migrate). A `Sunset` header will appear when a shutdown date is set.
