Skip to content
View as Markdown

Quickstart

Four steps from an API key to a delivered message. Every call here is copy-paste ready.

On this page

1. Get a key, then check it

Create a key in the Project Blue dashboard under Settings → API Keys. Keys start with proj_ followed by 64 hex characters, and you may hold five active keys at a time.

The full key is shown once
The value is returned only at creation. Every later view is masked to the first 12 and last 4 characters, so store it somewhere durable now. If you lose it, delete the key and make a new one.

Start with /get-lines. It is the right first call because it reads rather than sends, and because its answer tells you which of the two setups you are in.

You got
It means
Do next
A JSON array
Paid account with its own line
Note a lineId, skip to step 3
An object with trial: true
Trial account on the shared line
Do step 2 first
401
The key never reached us, or was rejected
See below

The two 401 bodies mean different things. Missing or invalid Authorization header means the header was absent or lacked the Bearer prefix — the request never carried a key. Invalid API key means the header was well-formed but the key is wrong, revoked, or from another account.

2. Get a number you can text

On a paid account, skip this — any valid number works.

On a trial, sends route through a shared Project Blue line and can only reach verified destinations. Verification is confirmed by an inbound text: the owner of that number must text the shared line from their own phone. Nothing in the API or the dashboard can confirm it on their behalf.

Testing on your own? Verify yourself in a minute
Register your own mobile number in the webapp under Settings, text the shared line from that phone, then send to yourself in step 3. That is the whole loop, and it needs nobody else.

Sending to an unverified destination returns 403 with Trial accounts can only message numbers verified on the shared line. See Trial accounts for what else is restricted.

3. Send the message

Two fields is the whole request. If the recipient has iMessage it arrives as one; otherwise it falls back to SMS automatically.

A success returns status: "done" — but no message id, which is why there is a step 4.

4. Confirm it landed

The send response tells you the message was accepted, not that it was delivered. Read it back from list messages filtered to the number you texted, and check data[0].status. The same row carries the message_handle you need for get a message.

In production, register a webhook instead of polling.

Before you loop

  1. Re-running the same send is collapsed, not repeated. Identical text to the same number within the hour returns 200 with deduped: true and sends nothing. Change the text or pass a distinct idempotencyKey. This is the most common reason a first integration looks like it worked but nothing arrived.
  2. 60 requests per minute per key, then 429 with retryAfterSeconds.
  3. Numbers are normalized to E.164. Most formats are accepted on the way in; everything comes back as +15551234567.
  4. Trial sends are real messages on a line shared with other accounts. Do not load-test them — unverified probing burns iMessage reputation for everyone on that line.
Guides