Quickstart
Receive a message and send a reply through the VoiceLayer API in about 10 minutes, without a real phone.
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: 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 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.
- In the dashboard, open Developers → API keys and choose Create key.
- For "What is this key for?", pick Your servers or the SDK.
- Set Environment to Testing. Test keys start with
vl_test_. - Copy the key. It is shown once.
Save it in your shell. Every example on this site reads it from VOICELAYER_API_KEY:
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.
npm install @voicelayer/apiStep 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 between that person and your workspace.
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?"
}'{
"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:
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 "https://api.vlayers.ai/v1/conversations/$CONVERSATION_ID/messages?before=end&limit=20" \
-H "Authorization: Bearer $VOICELAYER_API_KEY"You see the inbound message: "Do you have a table for two tonight?"
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 -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?"
}
}'In test mode the answer looks like this (a live reply starts as queued):
{
"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.
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 -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."
}
}'{
"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 | 401 | Send Authorization: Bearer <key> with a secret key from the dashboard (Developers → API keys). Test keys start with vl_test_. |
sender_ambiguous | 409 | Name one of details.senders in from. |
test_address_required | 422 | Use a reserved test address, or a live key to reach a real person. |
test_mode_only | 403 | Use a test key (vl_test_…) from the dashboard. |
test_limit_reached | 429 | Try again after midnight UTC, or ask for a higher test limit. |