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

# Quickstart

> Get an API key, send your first message and see the reply come back, in about five minutes.

> [!NOTE]
>
> **Before you start**
>
> * **A Blujay workspace.** [Sign up free](https://app.blujay.io/?auth=register) if you don't have one. A new workspace starts on the Free Plan with a shared phone number.
> * **Workspace owner or admin access.** Only workspace owners and admins see the **API** tab.
> * **Your own US mobile number.** Blujay sends to US numbers only. Use your own phone as the recipient while you test: every API call sends real messages.
> * **The Blujay app, if you're on the Free Plan.** A Free Plan number can message up to 10 verified contacts: people who texted the number first and were verified in the app. Verification happens in the app, not through the API (step 4). A Dedicated number skips that step. See [Plans](/plans.md).

### 1. Get your API key

In [Blujay](https://app.blujay.io), open **API** in the sidebar. Under **API Token**, reveal and copy your key. It starts with `blu_`.

Keep it on your server, in an environment variable or secret manager. Never put it in a browser, a mobile app or source control.

```bash theme={null}
export BLUJAY_API_KEY="blu_your_api_key"
```

### 2. Check that the key works

```bash theme={null}
curl https://api.blujay.io/v1/me \
  -H "Authorization: Bearer $BLUJAY_API_KEY"
```

```json theme={null}
{
  "status": "success",
  "data": {
    "workspace_id": "workspace_abc123",
    "workspace_label": "Acme Support",
    "credential": { "type": "api_key", "id": "3f6c2a7e-1b9d-4e5a-9c1f-2d7b8a6e4c10", "status": "active" },
    "api_key": { "id": "3f6c2a7e-1b9d-4e5a-9c1f-2d7b8a6e4c10", "status": "active" }
  },
  "trace_id": "trc_9d1f0a7c2b4e",
  "timestamp": "2026-10-14T17:00:00.000Z"
}
```

A `401` means the key is missing or wrong. See [`UNAUTHORIZED`](/errors/unauthorized).

### 3. Pick a phone number to send from

```bash theme={null}
curl https://api.blujay.io/v1/phone-numbers \
  -H "Authorization: Bearer $BLUJAY_API_KEY"
```

Choose a number whose `send_status.allowed` is `true`, and note its `display_number`. `POST /v1/chats` takes that string as `from`. A Free Plan number shows `access_type: "shared"`. A Dedicated number also reports its live sending limits and queue. See [Phone numbers and limits](/phone-numbers.md).

### 4. Free Plan only: verify your phone

On the Free Plan, Blujay sends only to contacts who have texted your Free Plan number first and were verified in the app. Skip this step if your number is Dedicated.

1. In [Blujay](https://app.blujay.io), go to **Contacts** and choose **Add verified contact**.
2. Enter your own mobile number and name.
3. From your phone, text the message Blujay shows to your Free Plan number. Scan the QR code to fill it in for you.
4. Wait until Blujay shows you as verified.

Sending to someone who isn't verified returns [`403 CONTACT_NOT_VERIFIED`](/errors/contact-not-verified).

### 5. Send your first message

`POST /v1/chats` starts a chat and sends the first message. Replace `from` with your Blujay number and `to` with your own mobile number.

```bash curl theme={null}
curl https://api.blujay.io/v1/chats \
  -H "Authorization: Bearer $BLUJAY_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: quickstart-0001" \
  -d '{
    "from": "+15555550100",
    "to": ["+15555550123"],
    "message": {
      "parts": [{ "type": "text", "text": "Hi! This is my first message from the Blujay API." }]
    }
  }'
```

```javascript Node.js theme={null}
const response = await fetch("https://api.blujay.io/v1/chats", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.BLUJAY_API_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": "quickstart-0001",
  },
  body: JSON.stringify({
    from: "+15555550100",
    to: ["+15555550123"],
    message: {
      parts: [{ type: "text", text: "Hi! This is my first message from the Blujay API." }],
    },
  }),
});

const body = await response.json();
if (body.status === "error") {
  throw new Error(`${body.error.code}: ${body.error.message} (trace ${body.trace_id})`);
}
console.log(response.status, body.data.chat_id, body.data.message.id);
```

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

response = requests.post(
    "https://api.blujay.io/v1/chats",
    headers={
        "Authorization": f"Bearer {os.environ['BLUJAY_API_KEY']}",
        "Idempotency-Key": "quickstart-0001",
    },
    json={
        "from": "+15555550100",
        "to": ["+15555550123"],
        "message": {
            "parts": [{"type": "text", "text": "Hi! This is my first message from the Blujay API."}]
        },
    },
    timeout=30,
)

body = response.json()
if body["status"] == "error":
    raise RuntimeError(f"{body['error']['code']}: {body['error']['message']} (trace {body['trace_id']})")
print(response.status_code, body["data"]["chat_id"], body["data"]["message"]["id"])
```

```go Go theme={null}
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"os"
)

func main() {
	payload := map[string]any{
		"from": "+15555550100",
		"to":   []string{"+15555550123"},
		"message": map[string]any{
			"parts": []map[string]string{
				{"type": "text", "text": "Hi! This is my first message from the Blujay API."},
			},
		},
	}
	body, _ := json.Marshal(payload)

	req, _ := http.NewRequest("POST", "https://api.blujay.io/v1/chats", bytes.NewReader(body))
	req.Header.Set("Authorization", "Bearer "+os.Getenv("BLUJAY_API_KEY"))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Idempotency-Key", "quickstart-0001")

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()

	var result struct {
		Status string `json:"status"`
		Error  struct {
			Code    string `json:"code"`
			Message string `json:"message"`
		} `json:"error"`
		TraceID string `json:"trace_id"`
		Data    struct {
			ChatID  string `json:"chat_id"`
			Message struct {
				ID string `json:"id"`
			} `json:"message"`
		} `json:"data"`
	}
	json.NewDecoder(resp.Body).Decode(&result)
	if result.Status == "error" {
		panic(fmt.Sprintf("%s: %s (trace %s)", result.Error.Code, result.Error.Message, result.TraceID))
	}
	fmt.Println(resp.StatusCode, result.Data.ChatID, result.Data.Message.ID)
}
```

Blujay answers `202 Accepted`: the message is queued (`delivery_status: "queued"`), not yet delivered.

```json theme={null}
{
  "status": "success",
  "data": {
    "chat_id": "chat_abc123",
    "message": {
      "id": "msg_abc123",
      "direction": "outbound",
      "delivery_status": "queued",
      "preferred_service": null,
      "service": null,
      "from": "+15555550100",
      "to": ["+15555550123"],
      "created_at": "2026-10-14T17:01:00.000Z",
      "parts": [{ "type": "text", "text": "Hi! This is my first message from the Blujay API." }]
    }
  },
  "trace_id": "trc_4c8e2a91f0d7",
  "timestamp": "2026-10-14T17:01:00.000Z"
}
```

`service` is `null` until Blujay confirms whether the message went out as iMessage, RCS or SMS. If your request times out, send it again with the **same** `Idempotency-Key`: you get the original result back instead of a second message.

> [!WARNING]
>
> **Common mistakes**
>
> * `from` is one string (the `display_number`); `to` is an array, even for one recipient.
> * `202` means queued. Nothing has been delivered yet.
> * A `400` such as [`VALIDATION_ERROR`](/errors/validation-error) has no `data`. Check `status === "error"` before you read `data`, as the samples above do.

### 6. Watch it get delivered

```bash theme={null}
curl https://api.blujay.io/v1/messages/msg_abc123 \
  -H "Authorization: Bearer $BLUJAY_API_KEY"
```

```json theme={null}
{
  "status": "success",
  "data": {
    "message_id": "msg_abc123",
    "chat_id": "chat_abc123",
    "contact_id": "contact_abc123",
    "phone_number_id": "phone_abc123",
    "preferred_service": null,
    "service": "iMessage",
    "status": "delivered",
    "parts": [{ "type": "text", "text": "Hi! This is my first message from the Blujay API." }],
    "degradations": [],
    "reply_to": null,
    "edited_at": null,
    "edit_count": 0,
    "unsent_at": null,
    "unsent_scope": null,
    "trace_id": "trc_4c8e2a91f0d7",
    "created_at": "2026-10-14T17:01:00.000Z",
    "updated_at": "2026-10-14T17:01:03.000Z"
  },
  "trace_id": "trc_51b9e07a3c6d",
  "timestamp": "2026-10-14T17:01:30.000Z"
}
```

The `202` in step 5 names the message `message.id` and reports `delivery_status: "queued"`; every read, including this one and the webhooks, calls it `message_id` and uses `status`, which starts at `accepted`. `status` moves to `sent`, and for iMessage and RCS on to `delivered` and `read`. `service` shows the service that carried it. Polling is fine for a first test; in production, [subscribe to webhooks](/webhooks/overview.md) instead. See [Track a message](/messaging/lifecycle.md).

### 7. Reply from your phone

Reply to the message from your phone, then list the chat's messages:

```bash theme={null}
curl "https://api.blujay.io/v1/chats/chat_abc123/messages?limit=10" \
  -H "Authorization: Bearer $BLUJAY_API_KEY"
```

Your reply is there as `msg_def456` with `status: "received"`.

### 8. Send a follow-up

Answer in the same chat with `POST /v1/chats/{chat_id}/messages`, using the `chat_id` from your first send:

```bash theme={null}
curl https://api.blujay.io/v1/chats/chat_abc123/messages \
  -H "Authorization: Bearer $BLUJAY_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: quickstart-0002" \
  -d '{
    "message": {
      "parts": [{ "type": "text", "text": "Got it, thanks for replying!" }]
    }
  }'
```

Blujay answers `202 Accepted` again. Now that the person has replied, follow-ups can include links, photos, files and contact cards, and run longer than 300 characters. Until a contact replies, Blujay rejects those with a `400`. See [Sending rules](/messaging/sending-rules.md) and [Send messages](/messaging/send-messages.md).

### 9. Get replies sent to you automatically

Instead of checking for new messages, have Blujay send each reply and delivery update to your server the moment it happens. Subscribe an HTTPS address on your server:

```bash theme={null}
curl https://api.blujay.io/v1/webhook-subscriptions \
  -H "Authorization: Bearer $BLUJAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://yourapp.example.com/webhooks/blujay",
    "event_types": ["message.received", "message.delivered", "message.failed"]
  }'
```

```json 201 Created theme={null}
{
  "status": "success",
  "data": {
    "subscription_id": "whsub_abc123",
    "url": "https://yourapp.example.com/webhooks/blujay",
    "event_types": ["message.received", "message.delivered", "message.failed"],
    "webhook_version": "2026-10-03",
    "status": "active",
    "phone_number_filter": { "mode": "all" },
    "signing_secret_hint": "Y3OA==",
    "signing_secret": "whsec_ZXhhbXBsZS1zZWNyZXQtZG8tbm90LXVzZS0xMjM0NTY3OA==",
    "created_at": "2026-10-14T17:05:00.000Z",
    "updated_at": "2026-10-14T17:05:00.000Z"
  },
  "trace_id": "trc_7e1c4b2a90f3",
  "timestamp": "2026-10-14T17:05:00.000Z"
}
```

> [!WARNING]
>
> The response includes `signing_secret`, shown **only this once**. Save it now: you need it to [check that each webhook really came from Blujay](/webhooks/verify-signatures.md).

Reply from your phone again, and your server receives a `message.received` event within seconds. That is the whole loop: you sent a message, and the reply arrived back as `message.received`. Testing on your laptop? A tunneling tool such as [ngrok](https://ngrok.com) gives your local server a temporary public HTTPS address. See [Webhooks](/webhooks/overview.md) for the full setup and [Receive messages](/messaging/receive-messages.md) for what inbound messages look like.

### 10. Build something

* Start from a working program: the [Examples](/examples.md) page has a complete reply bot and a safe batch sender you can copy.
* Building with an AI assistant? [Build with AI](/build-with-ai.md) has the machine-readable docs and a prompt that reviews your integration for duplicate sends, missed webhooks and leaked keys.

## Next steps

> **[Sending rules](/messaging/sending-rules.md)**
>
> What a message can contain before and after the contact replies.

> **[Send messages](/messaging/send-messages.md)**
>
> Follow-ups, links, media and choosing a service.

> **[Idempotency](/idempotency.md)**
>
> Retry safely without sending twice.

> **[Errors](/errors.md)**
>
> What each error code means and how to handle it.


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