# Quickstart

> Receive a message and send a reply through the VoiceLayer API in about 10 minutes, without a real phone.

Source: https://docs.vlayers.ai/get-started/quickstart

Kind: Tutorial

## What you'll build

You pretend to be a customer who texts your business, read that message with the API, and answer it. Everything runs in [test mode](https://docs.vlayers.ai/developers/test-mode): no real phone, no carrier, no charge. When you are done, the same code works with a live key and real people.

## Prerequisites

- A VoiceLayer account. Sign in to the dashboard at vlayers.ai.
- Nothing else: you don't need a phone number. Test mode gives your workspace a free [test sender](https://docs.vlayers.ai/developers/test-mode#your-free-test-sender) the first time it needs one.
- One of: a terminal with `curl`, Node.js 18 or later, or Python 3.9 or later.

## Step 1: Create a test key

A test key lets you use the real API without reaching a carrier or paying anything.

1. In the dashboard, open **Developers → API keys** and choose **Create key**.
2. For "What is this key for?", pick **Your servers or the SDK**.
3. Set **Environment** to **Testing**. Test keys start with `vl_test_`.
4. Copy the key. It is shown once.

Save it in your shell. Every example on this site reads it from `VOICELAYER_API_KEY`:

```bash
export VOICELAYER_API_KEY=vl_test_...
```

## Step 2: Install an SDK (optional)

The examples come in curl, TypeScript and Python. Skip this step if you use curl.

The TypeScript examples are ES modules that use top-level `await`. In a new project, run `npm pkg set type=module` once, then save an example as `example.ts` and run it with `npx tsx example.ts`. The Python examples run with `python example.py`.

TypeScript:
```bash
npm install @voicelayer/api
```

Python:
```bash
pip install voicelayer
```

> Publishing soon: voicelayer is not on PyPI yet. Until it is, install it from a checkout of the VoiceLayer repository (`sdks/python/voicelayer`), as shown.

```bash
pip install ./voicelayer/sdks/python/voicelayer
```

## Step 3: Simulate a customer writing to you

`POST /v1/test/inbound` plays the part of a person texting your number from a reserved test address (`+15550100` to `+15550199`). It opens a [conversation](https://docs.vlayers.ai/get-started/concepts#conversation) between that person and your workspace.

cURL:

```bash
curl -X POST https://api.vlayers.ai/v1/test/inbound \
  -H "Authorization: Bearer $VOICELAYER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "channel": "sms",
    "from": "+15550123",
    "text": "Do you have a table for two tonight?"
  }'
```

TypeScript:

```ts
import { createVoiceLayerClient, createTestInbound } from '@voicelayer/api';

const client = createVoiceLayerClient({ apiKey: process.env.VOICELAYER_API_KEY!, throwOnError: true });

const { data } = await createTestInbound({
  client,
  body: {
    channel: 'sms',
    from: '+15550123',
    text: 'Do you have a table for two tonight?'
  }
});
console.log(data.conversationId);
```

Python:

```python
import os
import voicelayer

config = voicelayer.Configuration(access_token=os.environ["VOICELAYER_API_KEY"])
with voicelayer.ApiClient(config) as client:
    api = voicelayer.TestApi(client)
    result = api.create_test_inbound(
        create_test_inbound_request=voicelayer.CreateTestInboundRequest.from_dict({
            "channel": "sms",
            "from": "+15550123",
            "text": "Do you have a table for two tonight?",
        }),
    )
    print(result.conversation_id)
```

Response `202`:

```json
{
  "conversationId": "0192f5b0-7c3e-7a51-9b2d-4e8f1a6c3d21",
  "messageId": "0192f5b0-8d1f-7b62-8c3e-5f9a2b7d4e32",
  "test": true
}
```

Copy the `conversationId` and save it. The next steps read it:

```bash
export CONVERSATION_ID=<the conversationId you got>
```

## Step 4: Read the message

`GET /v1/conversations/{id}/messages` returns the conversation's messages. `before=end` asks for the newest page.

cURL:

```bash
curl "https://api.vlayers.ai/v1/conversations/$CONVERSATION_ID/messages?before=end&limit=20" \
  -H "Authorization: Bearer $VOICELAYER_API_KEY"
```

TypeScript:

```ts
import { createVoiceLayerClient, listConversationMessages } from '@voicelayer/api';

const client = createVoiceLayerClient({ apiKey: process.env.VOICELAYER_API_KEY!, throwOnError: true });

const { data } = await listConversationMessages({
  client,
  path: {
    id: process.env.CONVERSATION_ID!
  },
  query: {
    before: 'end',
    limit: 20
  }
});
console.log(data.messages);
```

Python:

```python
import os
import voicelayer

config = voicelayer.Configuration(access_token=os.environ["VOICELAYER_API_KEY"])
with voicelayer.ApiClient(config) as client:
    api = voicelayer.ConversationsApi(client)
    result = api.list_conversation_messages(
        id=os.environ["CONVERSATION_ID"],
        before="end",
        limit=20,
    )
    print(result.messages)
```

You see the inbound message: "Do you have a table for two tonight?"

> **Note:** If the number's agent is set to answer, it replies too (in test mode, at no cost), so you may see its answer here. To answer only from your code, [hand the conversation to your code](https://docs.vlayers.ai/developers/handoff).

## Step 5: Reply

`POST /v1/conversations/{id}/messages` answers in the conversation. The channel, your number and the person's number come from the conversation, so you only send the text.

cURL:

```bash
curl -X POST "https://api.vlayers.ai/v1/conversations/$CONVERSATION_ID/messages" \
  -H "Authorization: Bearer $VOICELAYER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": {
      "type": "text",
      "text": "Yes, we have a table at 7pm. Shall I book it?"
    }
  }'
```

TypeScript:

```ts
import { createVoiceLayerClient, replyToConversation } from '@voicelayer/api';

const client = createVoiceLayerClient({ apiKey: process.env.VOICELAYER_API_KEY!, throwOnError: true });

const { data } = await replyToConversation({
  client,
  path: {
    id: process.env.CONVERSATION_ID!
  },
  body: {
    content: {
      type: 'text',
      text: 'Yes, we have a table at 7pm. Shall I book it?'
    }
  }
});
console.log(data.message.id);
```

Python:

```python
import os
import voicelayer

config = voicelayer.Configuration(access_token=os.environ["VOICELAYER_API_KEY"])
with voicelayer.ApiClient(config) as client:
    api = voicelayer.ConversationsApi(client)
    result = api.reply_to_conversation(
        id=os.environ["CONVERSATION_ID"],
        reply_to_conversation_request=voicelayer.ReplyToConversationRequest.from_dict({
            "content": {
                "type": "text",
                "text": "Yes, we have a table at 7pm. Shall I book it?",
            },
        }),
    )
    print(result.message.id)
```

In test mode the answer looks like this (a live reply starts as `queued`):

```json
{
  "message": {
    "id": "8d1f6b62-3c8e-4f9a-b7d4-e325f9a2b7d4",
    "conversationId": "7c3e7a51-9b2d-4e8f-a6c3-d21f5b0c7c3e",
    "channel": "sms",
    "direction": "outbound",
    "status": "sent",
    "text": "Yes, we have a table at 7pm. Shall I book it?",
    "parts": [{ "id": "8d1f6b62-3c8e-4f9a-b7d4-e325f9a2b7d4", "status": "sent" }]
  }
}
```

The full message has more fields: see [Reply in a conversation](https://docs.vlayers.ai/reference/conversations/reply-to-conversation).

The answer is `202 Accepted`. A live message starts as `queued`; in test mode the reply goes to a simulated carrier at once, so you see `sent`. It never reaches a real phone, never becomes `delivered`, and costs nothing.

## Step 6: See the whole conversation

Run the command from Step 4 again. You now see both messages: the customer's question and your reply, with its `status`.

## Test it: send a message first

You can also message someone first. This texts the reserved number `+15550123` directly. If you used that address in Step 3, the message joins the same conversation: a conversation is one of your senders and one person.

cURL:

```bash
curl -X POST https://api.vlayers.ai/v1/messages \
  -H "Authorization: Bearer $VOICELAYER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "channel": "sms",
    "to": "+15550123",
    "content": {
      "type": "text",
      "text": "Your table for two is booked for 7pm."
    }
  }'
```

TypeScript:

```ts
import { createVoiceLayerClient, sendMessage } from '@voicelayer/api';

const client = createVoiceLayerClient({ apiKey: process.env.VOICELAYER_API_KEY!, throwOnError: true });

const { data } = await sendMessage({
  client,
  body: {
    channel: 'sms',
    to: '+15550123',
    content: {
      type: 'text',
      text: 'Your table for two is booked for 7pm.'
    }
  }
});
console.log(data.message.id);
```

Python:

```python
import os
import voicelayer

config = voicelayer.Configuration(access_token=os.environ["VOICELAYER_API_KEY"])
with voicelayer.ApiClient(config) as client:
    api = voicelayer.MessagesApi(client)
    result = api.send_message(
        send_message_request=voicelayer.SendMessageRequest.from_dict({
            "channel": "sms",
            "to": "+15550123",
            "content": {
                "type": "text",
                "text": "Your table for two is booked for 7pm.",
            },
        }),
    )
    print(result.message.id)
```

Response `202`:

```json
{
  "message": {
    "id": "0192f5b0-8d1f-7b62-8c3e-5f9a2b7d4e32",
    "conversationId": "0192f5b0-7c3e-7a51-9b2d-4e8f1a6c3d21",
    "channel": "sms",
    "direction": "outbound",
    "status": "queued",
    "text": "Your table for two is booked for 7pm.",
    "media": [],
    "from": "+14155550100",
    "to": "+15550123",
    "bindingId": null,
    "metadata": {},
    "error": null,
    "createdAt": "2026-10-20T15:04:05.000Z",
    "parts": [
      {
        "id": "0192f5b0-8d1f-7b62-8c3e-5f9a2b7d4e32",
        "status": "queued"
      }
    ]
  }
}
```

## Common errors

| Error | Status | How to fix |
|---|---|---|
| [`unauthorized`](https://docs.vlayers.ai/errors/unauthorized) | 401 | Send `Authorization: Bearer <key>` with a secret key from the dashboard (Developers → API keys). Test keys start with `vl_test_`. |
| [`sender_ambiguous`](https://docs.vlayers.ai/errors/sender_ambiguous) | 409 | Name one of `details.senders` in `from`. |
| [`test_address_required`](https://docs.vlayers.ai/errors/test_address_required) | 422 | Use a reserved test address, or a live key to reach a real person. |
| [`test_mode_only`](https://docs.vlayers.ai/errors/test_mode_only) | 403 | Use a test key (`vl_test_…`) from the dashboard. |
| [`test_limit_reached`](https://docs.vlayers.ai/errors/test_limit_reached) | 429 | Try again after midnight UTC, or ask for a higher test limit. |

## Next steps

- [Receive messages with webhooks](https://docs.vlayers.ai/developers/receive/webhooks): Get an HTTPS request the moment someone writes to you.
- [Send an SMS](https://docs.vlayers.ai/developers/send/sms): Send to real phone numbers with a live key.
- [Core concepts](https://docs.vlayers.ai/get-started/concepts): Senders, conversations and who answers them.
- [Connect an AI agent](https://docs.vlayers.ai/developers/mcp): Give Claude or ChatGPT your channels over MCP.
