# Quickstart

By the end you will have an agent that replies to a contact, and a webhook endpoint on your server that receives the reply. The conversation runs by API on the `api` channel, so nothing reaches a real person.

Every example reads the key from `LELEMON_API_KEY`. Node.js examples use the [TypeScript SDK](/tools/sdk), which sends an `Idempotency-Key` on every `POST` for you.

## 1. Get a key

Create a key in the dashboard under Settings, Integrations. The secret is shown once.

The key needs these scopes: `agents:read`, `agents:write`, `pipelines:write`, `contacts:write`, `cases:read`, `cases:write`, `cases:converse`, `webhooks:write`. Check it:

```bash title="cURL"
curl https://api.lelemon.cl/v2/me \
  -H "Authorization: Bearer $LELEMON_API_KEY"
```

```ts title="Node.js"
import { Lelemon } from '@lelemon-ai/sdk';

const lelemon = new Lelemon({ apiKey: process.env.LELEMON_API_KEY });
const me = await lelemon.me.retrieve();
console.log(me.organization.id);
```


## 2. Create an agent

An agent needs instructions and a model. `GET /v2/models` lists the models you can use. The model is a public id with the form `author/name`, such as `anthropic/claude-sonnet-5.5`, `deepseek/deepseek-v3.2` or `amazon/nova-2-lite`. It does not change when Lelemon changes its provider or region. Each model also lists its context window in tokens, whether `thinking: "off"` is accepted and `deprecated`, the date it stops being accepted or `null`.

```bash title="cURL"
curl https://api.lelemon.cl/v2/agents \
  -H "Authorization: Bearer $LELEMON_API_KEY" \
  -H "Idempotency-Key: quickstart-agent-1" \
  --json '{
    "name": "Order bot",
    "instructions": "You answer questions about orders in one short sentence.",
    "llmConfig": { "model": "anthropic/claude-sonnet-5.5" }
  }'
```

```ts title="Node.js"
const agent = await lelemon.agents.create({
  name: 'Order bot',
  instructions: 'You answer questions about orders in one short sentence.',
  llmConfig: { model: 'anthropic/claude-sonnet-5.5' },
});
```


Do not send `provider`: it comes from the model. The agent answers with the same public id in `llmConfig.model`, and so do usage, turns and the `case.turn_completed` event. If you saved ids such as `us.anthropic.claude-sonnet-4-6` or send `provider`, see [Model ids](/basics/versioning#model-ids).

The agent starts as a `draft`. Every later change to its configuration creates a new version.

## 3. Create a pipeline and connect the agent

A pipeline is the path a conversation follows. Each stage can have the agent that works it, and the entry stage is where new cases start. Create the pipeline with its stages in one call:

```bash title="cURL"
curl https://api.lelemon.cl/v2/pipelines \
  -H "Authorization: Bearer $LELEMON_API_KEY" \
  -H "Idempotency-Key: quickstart-pipeline-1" \
  --json '{
    "name": "Orders",
    "stages": [
      { "name": "Ordering", "entry": true, "agentId": "agt_..." },
      { "name": "Done" }
    ]
  }'
```

```ts title="Node.js"
const pipeline = await lelemon.pipelines.create({
  name: 'Orders',
  stages: [
    { name: 'Ordering', entry: true, agentId: agent.id },
    { name: 'Done' },
  ],
});
```


Activate the agent. If something still keeps people from reaching it, `readiness.missing` lists it; an empty list means the agent is reachable. The cases you open from the API need the agent to have the `api` channel. An agent created with `channels: ["whatsapp"]` only comes out with `ready: false` and `no_api_channel` in `missing` until you add `api` to its channels with `PATCH /v2/agents/{id}`. Add `?dryRun=true` to see the readiness without turning the agent on.

```bash title="cURL"
curl -X POST https://api.lelemon.cl/v2/agents/agt_.../activate \
  -H "Authorization: Bearer $LELEMON_API_KEY"
```

```ts title="Node.js"
const active = await lelemon.agents.activate(agent.id);
console.log(active.readiness);
```


## 4. Have a conversation

Create the contact, open a case for it with the agent, and write as the contact. A message with `author: "contact"` makes the agent answer.

```bash title="cURL"
curl https://api.lelemon.cl/v2/contacts \
  -H "Authorization: Bearer $LELEMON_API_KEY" \
  -H "Idempotency-Key: quickstart-contact-1" \
  --json '{ "name": "Ana Rojas", "externalRef": "cust-88123" }'

curl https://api.lelemon.cl/v2/cases \
  -H "Authorization: Bearer $LELEMON_API_KEY" \
  -H "Idempotency-Key: quickstart-case-1" \
  --json '{ "contactId": "ctc_...", "agentId": "agt_..." }'

curl https://api.lelemon.cl/v2/cases/cse_.../messages \
  -H "Authorization: Bearer $LELEMON_API_KEY" \
  -H "Idempotency-Key: quickstart-message-1" \
  --json '{ "author": "contact", "text": "Where is my order?" }'
```

```ts title="Node.js"
const contact = await lelemon.contacts.create({ name: 'Ana Rojas', externalRef: 'cust-88123' });
const kase = await lelemon.cases.create({ contactId: contact.id, agentId: agent.id });
await lelemon.caseMessages.create(kase.id, { author: 'contact', text: 'Where is my order?' });
```


The request returns the contact's message. The agent answers in the background and the answer reaches you through the `message.created` webhook (step 5). You can also read the thread:

```bash title="cURL"
curl "https://api.lelemon.cl/v2/cases/cse_.../messages?order=asc" \
  -H "Authorization: Bearer $LELEMON_API_KEY"
```


`agentState` on the case tells you what the agent is doing: `thinking` and `replying` mean it is working, `waiting_for_contact` means it answered.

## 5. Receive a webhook

Register an endpoint. You can point it at `localhost` only if the API runs with `LELEMON_ALLOW_LOCAL_WEBHOOKS=true`; otherwise use a public URL such as a tunnel.

```bash title="cURL"
curl https://api.lelemon.cl/v2/webhook-endpoints \
  -H "Authorization: Bearer $LELEMON_API_KEY" \
  -H "Idempotency-Key: quickstart-webhook-1" \
  --json '{ "url": "https://example.com/lelemon", "enabledEvents": ["message.created"] }'
```

```ts title="Node.js"
const endpoint = await lelemon.webhookEndpoints.create({
  url: 'https://example.com/lelemon',
  enabledEvents: ['message.created'],
});
console.log(endpoint.secret);
```


The response includes `secret`, shown only once. Store it. Your server verifies each delivery with the raw body:

```ts title="Node.js (Express)"
import express from 'express';
import { Lelemon } from '@lelemon-ai/sdk';

const app = express();

app.post('/lelemon', express.raw({ type: 'application/json' }), async (req, res) => {
  const event = await Lelemon.webhooks.constructEvent(
    req.body,
    req.header('Lelemon-Signature') ?? '',
    process.env.LELEMON_WEBHOOK_SECRET!,
  );
  res.sendStatus(200);
  console.log(event.type, event.data);
});
```


Send a signed test event to check the whole path:

```bash title="cURL"
curl -X POST https://api.lelemon.cl/v2/webhook-endpoints/whk_.../test \
  -H "Authorization: Bearer $LELEMON_API_KEY"
```


## Next

- [Set up an account from zero](/get-started/setup-from-zero): the whole configuration, with fields, skills, knowledge, tools and channels, ready for real customers.
- Copy a complete setup from the [use cases](/use-cases).
- [Run Lelemon for your customers](/guides/platforms) with subaccounts.
- [Message on events](/guides/events-and-triggers) instead of waiting for the contact to write.
- [Verify signatures](/webhooks/verify) and [handle retries](/webhooks/receive).
- Switch to a `lel_live_` key when you launch.
