> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lazyapp.in/llms.txt
> Use this file to discover all available pages before exploring further.

# Quickstart

> Authenticate, confirm a connection, and send your first WhatsApp message in minutes.

LazyApp is one WhatsApp API for developers, SaaS platforms, and businesses: send messages and templates, receive signed webhooks, run OTP verify, broadcasts, and flows — and white-label the same platform for your own customers. This page takes you from API key to a confirmed send — about five minutes.

**Base URL:** `https://lazyapp.in`

> [!NOTE]
>
> **Building a product where your users connect *their* WhatsApp numbers?** The send loop below is the same, but you'll want one workspace per platform customer, scoped keys, and webhook routing — start at the [platform guide](https://docs.lazyapp.in/platform/overview.md) after your first message.

***

## Authentication

All API requests require an API key as a Bearer token. There is no official SDK yet — every example below is plain HTTP.

### Get your API key

1. [Sign up](https://lazyapp.in) or log in
2. Open **Settings → API keys**
3. Click **Create API key**
4. Copy the key immediately — you won't be able to see it again

Use a `sk_test_` key while you build. Test keys never reach a real phone, and they are free: test mode is never plan-limited. A `sk_live_` key needs the Pro plan and returns `402` `plan_limit_reached` without it. See [Plans and limits](https://docs.lazyapp.in/billing/pricing-and-tiers.md).

### Set up the client

```bash cURL theme={null}
export LAZYAPP_API_KEY="sk_test_..."

curl https://lazyapp.in/v1/capabilities \
  -H "Authorization: Bearer $LAZYAPP_API_KEY"
```

```js Node theme={null}
const LAZYAPP_API_KEY = process.env.LAZYAPP_API_KEY;

const response = await fetch('https://lazyapp.in/v1/capabilities', {
  headers: { Authorization: `Bearer ${LAZYAPP_API_KEY}` },
});

const { data } = await response.json();
```

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

LAZYAPP_API_KEY = os.environ["LAZYAPP_API_KEY"]

response = requests.get(
    "https://lazyapp.in/v1/capabilities",
    headers={"Authorization": f"Bearer {LAZYAPP_API_KEY}"},
)

data = response.json()["data"]
```

```php PHP theme={null}
$response = Http::withToken(env('LAZYAPP_API_KEY'))
    ->get('https://lazyapp.in/v1/capabilities');

$data = $response->json('data');
```

**Key format:** `sk_test_` or `sk_live_` plus 40 characters. Keys are stored as SHA-256 hashes — they're only shown once at creation.

**Security tips:** Use environment variables, create separate keys per app, grant the narrowest [scopes](https://docs.lazyapp.in/reference/scopes.md), and rotate periodically. See [Authentication](https://docs.lazyapp.in/authentication.md).

> [!WARNING]
>
> Never put a key in frontend code. If you need LazyApp in a browser, see
> [embedded components](https://docs.lazyapp.in/platform/embedded-components.md).

***

## Key concepts

* **Workspace** — Your team tenant. Your API key belongs to exactly one workspace; you never pass a workspace id.
* **Mode** — `live` or `test`, taken from the key prefix. Test never reaches a real phone. Data in the two modes never mixes.
* **Connection** — A WhatsApp Business Account plus its phone numbers inside the workspace.
* **Message** — Anything you send or receive. Addressed by a stable `uuid`.
* **Webhook** — How LazyApp pushes delivery, reads, replies, and template status to you.

IDs in this API are UUIDs (like `9f8b1c2d-4e5f-4a6b-8c7d-0e1f2a3b4c5d`). Each step below returns what the next step needs.

***

## Step 1: Confirm a connection

You need at least one WhatsApp connection on the workspace. Claim the free LazyApp sandbox number in test mode, or connect your own number in the console (that needs a plan).

Then list what you have:

```bash cURL theme={null}
curl https://lazyapp.in/v1/capabilities \
  -H "Authorization: Bearer $LAZYAPP_API_KEY"
```

```js Node theme={null}
const response = await fetch('https://lazyapp.in/v1/capabilities', {
  headers: { Authorization: `Bearer ${process.env.LAZYAPP_API_KEY}` },
});

const { data } = await response.json();

for (const connection of data) {
  console.log(`${connection.display_phone_number}: ${connection.connection_uuid}`);
}
// → +919812345670: 9f8b1c2d-4e5f-4a6b-8c7d-0e1f2a3b4c5d
```

```python Python theme={null}
result = requests.get(
    "https://lazyapp.in/v1/capabilities",
    headers={"Authorization": f"Bearer {os.environ['LAZYAPP_API_KEY']}"},
).json()

for connection in result["data"]:
    print(f"{connection['display_phone_number']}: {connection['connection_uuid']}")
# → +919812345670: 9f8b1c2d-4e5f-4a6b-8c7d-0e1f2a3b4c5d
```

```php PHP theme={null}
$data = Http::withToken(env('LAZYAPP_API_KEY'))
    ->get('https://lazyapp.in/v1/capabilities')
    ->json('data');

foreach ($data as $connection) {
    echo "{$connection['display_phone_number']}: {$connection['connection_uuid']}\n";
}
```

```json theme={null}
{
  "data": [
    {
      "connection_uuid": "9f8b1c2d-4e5f-4a6b-8c7d-0e1f2a3b4c5d",
      "phone_number_id": "123456789012345",
      "display_phone_number": "+919812345670",
      "is_default": true,
      "capabilities": {
        "messages.text": true,
        "messages.template": true
      }
    }
  ]
}
```

Most sends use the default connection automatically. Pass `phone_number_id` or `connection_uuid` only when you have several. See [Capabilities](https://docs.lazyapp.in/concepts/capabilities.md).

***

## Step 2: Send your first message

```bash cURL theme={null}
curl https://lazyapp.in/v1/messages \
  -H "Authorization: Bearer $LAZYAPP_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: quickstart-1" \
  -d '{
    "type": "text",
    "to": "+919812345670",
    "body": "Hello from LazyApp."
  }'
```

```js Node theme={null}
const response = await fetch('https://lazyapp.in/v1/messages', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.LAZYAPP_API_KEY}`,
    'Content-Type': 'application/json',
    'Idempotency-Key': 'quickstart-1',
  },
  body: JSON.stringify({
    type: 'text',
    to: '+919812345670',
    body: 'Hello from LazyApp.',
  }),
});

const message = await response.json();
console.log('Message sent:', message.uuid);
```

```python Python theme={null}
message = requests.post(
    "https://lazyapp.in/v1/messages",
    headers={
        "Authorization": f"Bearer {os.environ['LAZYAPP_API_KEY']}",
        "Idempotency-Key": "quickstart-1",
    },
    json={
        "type": "text",
        "to": "+919812345670",
        "body": "Hello from LazyApp.",
    },
).json()

print(f"Message sent: {message['uuid']}")
```

```php PHP theme={null}
$message = Http::withToken(env('LAZYAPP_API_KEY'))
    ->withHeader('Idempotency-Key', 'quickstart-1')
    ->post('https://lazyapp.in/v1/messages', [
        'type' => 'text',
        'to' => '+919812345670',
        'body' => 'Hello from LazyApp.',
    ])
    ->json();

echo "Message sent: {$message['uuid']}\n";
```

A `201` means LazyApp accepted and sent it:

```json theme={null}
{
  "uuid": "9f8b1c2d-4e5f-4a6b-8c7d-0e1f2a3b4c5d",
  "request_id": "req_01J9Z4K7Q0",
  "status": "sent",
  "acceptance_status": "accepted",
  "type": "text",
  "provider_message_id": "wamid.HBgMOTE5ODEyMzQ1Njcw",
  "error_message": null
}
```

Save the `uuid` — you need it for the next step.

> [!NOTE]
>
> A free-form text message only reaches someone inside the 24-hour customer-service
> window — after they message you. Outside it, send an approved
> [template](https://docs.lazyapp.in/sending/templates.md) instead. LazyApp enforces this server-side, so a send
> outside the window fails with a clear error rather than silently vanishing.

***

## Step 3: Confirm it worked

Fetch the message back — its `status` tells you where it is in the pipeline:

```bash cURL theme={null}
curl "https://lazyapp.in/v1/messages/MESSAGE_UUID" \
  -H "Authorization: Bearer $LAZYAPP_API_KEY"
```

```js Node theme={null}
const check = await fetch(
  `https://lazyapp.in/v1/messages/${message.uuid}`,
  { headers: { Authorization: `Bearer ${process.env.LAZYAPP_API_KEY}` } },
).then((r) => r.json());

console.log(check.status);
// → sent
```

```python Python theme={null}
check = requests.get(
    f"https://lazyapp.in/v1/messages/{message['uuid']}",
    headers={"Authorization": f"Bearer {os.environ['LAZYAPP_API_KEY']}"},
).json()

print(check["status"])
# → sent
```

```php PHP theme={null}
$check = Http::withToken(env('LAZYAPP_API_KEY'))
    ->get("https://lazyapp.in/v1/messages/{$message['uuid']}")
    ->json();

echo $check['status']; // sent
```

`status` moves through `queued` → `sent` → `delivered` → `read` (or `played` / `failed`). You'll also see the message in the [console](https://lazyapp.in). Prefer [webhooks](https://docs.lazyapp.in/webhooks/overview.md) for production instead of polling.

**That's the whole loop** — connection, send, confirmation. Everything else is variations on this.

***

## One endpoint, every send mode

The same `POST /v1/messages` call covers immediate sends, scheduling, and every message type:

| You set | Result |
| - | - |
| No `send_at` | Sends immediately (`201`) |
| `send_at` (+ optional `ttl_seconds`) | Schedules for later (`202`) |
| `type: "template"` | Works outside the 24-hour window |
| `type: "text"` / media / interactive / … | Free-form, window must be open |

```json theme={null}
{
  "type": "template",
  "to": "+919812345670",
  "template": { "name": "appointment_reminder", "language": "en" },
  "send_at": "2026-09-02T09:00:00+05:30",
  "ttl_seconds": 3600
}
```

Message types, media, reactions, and quoting are covered in [Messages](https://docs.lazyapp.in/sending/messages.md).

***

## Step 4: Receive status on a webhook

Register an endpoint so delivery and reads push to you:

```bash cURL theme={null}
curl https://lazyapp.in/v1/webhooks \
  -H "Authorization: Bearer $LAZYAPP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://yourapp.example/webhooks/lazyapp",
    "subscribed_events": ["message.delivered", "message.read", "message.failed"]
  }'
```

```js Node theme={null}
await fetch('https://lazyapp.in/v1/webhooks', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.LAZYAPP_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    url: 'https://yourapp.example/webhooks/lazyapp',
    subscribed_events: ['message.delivered', 'message.read', 'message.failed'],
  }),
});
```

```python Python theme={null}
requests.post(
    "https://lazyapp.in/v1/webhooks",
    headers={"Authorization": f"Bearer {os.environ['LAZYAPP_API_KEY']}"},
    json={
        "url": "https://yourapp.example/webhooks/lazyapp",
        "subscribed_events": [
            "message.delivered",
            "message.read",
            "message.failed",
        ],
    },
)
```

```php PHP theme={null}
Http::withToken(env('LAZYAPP_API_KEY'))
    ->post('https://lazyapp.in/v1/webhooks', [
        'url' => 'https://yourapp.example/webhooks/lazyapp',
        'subscribed_events' => ['message.delivered', 'message.read', 'message.failed'],
    ]);
```

The response includes a signing `secret`, shown once. Use it to [verify every delivery](https://docs.lazyapp.in/webhooks/verifying.md) before you trust the body. Leave `subscribed_events` out to receive [everything](https://docs.lazyapp.in/webhooks/events.md).

***

## Beyond messaging

Sending is the entry point. The same API key opens the rest of WhatsApp on LazyApp.

### Send a template outside the window

```bash cURL theme={null}
curl https://lazyapp.in/v1/messages \
  -H "Authorization: Bearer $LAZYAPP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "template",
    "to": "+919812345670",
    "template": {
      "name": "order_shipped",
      "language": "en",
      "category": "utility",
      "components": [
        {
          "type": "body",
          "parameters": [
            { "type": "text", "text": "1042" }
          ]
        }
      ]
    }
  }'
```

```js Node theme={null}
await fetch('https://lazyapp.in/v1/messages', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.LAZYAPP_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    type: 'template',
    to: '+919812345670',
    template: {
      name: 'order_shipped',
      language: 'en',
      category: 'utility',
      components: [
        {
          type: 'body',
          parameters: [{ type: 'text', text: '1042' }],
        },
      ],
    },
  }),
});
```

```python Python theme={null}
requests.post(
    "https://lazyapp.in/v1/messages",
    headers={"Authorization": f"Bearer {os.environ['LAZYAPP_API_KEY']}"},
    json={
        "type": "template",
        "to": "+919812345670",
        "template": {
            "name": "order_shipped",
            "language": "en",
            "category": "utility",
            "components": [
                {
                    "type": "body",
                    "parameters": [{"type": "text", "text": "1042"}],
                }
            ],
        },
    },
)
```

### Send an OTP

```bash cURL theme={null}
curl https://lazyapp.in/v1/otp \
  -H "Authorization: Bearer $LAZYAPP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"to": "+919812345670"}'
```

```js Node theme={null}
await fetch('https://lazyapp.in/v1/otp', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.LAZYAPP_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ to: '+919812345670' }),
});
```

```python Python theme={null}
requests.post(
    "https://lazyapp.in/v1/otp",
    headers={"Authorization": f"Bearer {os.environ['LAZYAPP_API_KEY']}"},
    json={"to": "+919812345670"},
)
```

See [Verify](https://docs.lazyapp.in/sending/verify.md) for managed vs delivery-only mode.

### Explore the rest

> **[Messages](https://docs.lazyapp.in/sending/messages.md)**
>
> Text, media, interactive, reactions, and scheduling

> **[Templates](https://docs.lazyapp.in/sending/templates.md)**
>
> Create, submit for Meta review, and sync status

> **[Webhooks](https://docs.lazyapp.in/webhooks/overview.md)**
>
> Signed delivery for messages, templates, and more

> **[Broadcasts](https://docs.lazyapp.in/sending/broadcasts.md)**
>
> Template sends to a segment, with quiet hours

> **[Verify](https://docs.lazyapp.in/sending/verify.md)**
>
> OTPs with fraud controls, managed or delivery-only

> **[Platforms](https://docs.lazyapp.in/platform/overview.md)**
>
> One workspace per customer, setup links, scoped keys

***

## What's next?

> **[Go live](https://docs.lazyapp.in/concepts/workspaces-and-modes.md)**
>
> Swap `sk_test_` for `sk_live_`. Nothing else changes.

> **[Idempotency](https://docs.lazyapp.in/concepts/idempotency.md)**
>
> Make every write safe to retry

> **[Errors](https://docs.lazyapp.in/concepts/errors.md)**
>
> Status codes and which ones are worth retrying

> **[API reference](https://docs.lazyapp.in/api-reference/messages/send-a-message.md)**
>
> Every endpoint, with a request playground

Going to production? Read [rate limits](https://docs.lazyapp.in/reference/rate-limits.md), [idempotency](https://docs.lazyapp.in/concepts/idempotency.md), [error handling](https://docs.lazyapp.in/concepts/errors.md), and [pricing](https://docs.lazyapp.in/billing/overview.md). Building for your users? Start at [Platforms](https://docs.lazyapp.in/platform/overview.md).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.