Developers
Line's public APIs let you build your own front end on top of a Line inbox. Every API below is described by an OpenAPI 3.1 document and listed in a machine-readable catalog, so an agent can discover and call them without reading this page.
APIs
Send, schedule, batch, and track SMS from approved Line numbers and sender names, with templates, opt-outs, lookups, and signed webhooks.
https://staging.useline.io/api/v1Start a visitor chat session from an authorised domain, send messages, and stream the assistant's replies.
https://staging.useline.io/api/channels/webchatSubmit a lead form. Line saves the response and Inbox entry, then processes notifications and workflow actions.
https://staging.useline.io/api/channels/webchat/formsQuickstart
Send a test text with the SMS Messaging API. A test key never calls the network or spends credits.
Get a test key
SMS API access needs approval. An organisation owner or admin requests it under Settings → API. After approval, create a test key there and keep it in an environment variable.
Settings → API → Request access Settings → API → Create key → Test export LINE_API_KEY=line_sk_test_…
Check the key works
A 200 response returns your account and the limits granted to the key.
curl -X GET 'https://staging.useline.io/api/v1/account' \ -H "Authorization: Bearer $LINE_API_KEY"
Send a test text
Put one of your SMS numbers in from. List them with GET /api/v1/phone-numbers.
Every send needs a unique Idempotency-Key, so a retry never sends twice. A queued response means Line accepted the work. It does not mean the handset received it.
curl -X POST 'https://staging.useline.io/api/v1/messages' \
-H "Authorization: Bearer $LINE_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H 'Content-Type: application/json' \
-d '{
"from": "+447700900123",
"to": "+447700900456",
"body": "Hi Sam, your engineer arrives between 10 and 12 tomorrow."
}'Follow the delivery
Read the message from status_url. In the sandbox it ends delivered, unless the recipient ends in a failure suffix.
curl -X GET 'https://staging.useline.io/api/v1/messages/6f1c2a9e-3b7d-4e2a-9c51-0d8e4f7a2b10' \ -H "Authorization: Bearer $LINE_API_KEY"
Sandbox
A test key calls the same URLs as a live key and gets the same responses. Line keeps its messages, templates, events and webhooks apart from live data. It never calls a carrier, debits the wallet or starts a workflow.
The last four digits of the recipient pick how a test send ends. Use Simulate a customer reply to test inbound texts, STOP and START.
Test end to end
- Create a webhook with the test key. Confirm it with Send a signed test event.
- Send to
+447700900456. Expectmessage.delivered. - Send to
+447700900002. Expectmessage.failed. - Simulate a reply. Expect
message.received, then read it withGET /api/v1/messages?direction=inbound. - Simulate
STOP, thenSTART. Expectcontact.opted_out, thencontact.opted_in. - Return a 500 from your endpoint once. Check the retry in the webhook's deliveries.
- Create a live key and a live webhook. Change nothing else.
The sandbox keeps no opt-out list. A test key can read the live list, but only a live key can change it.
failed · message.sent, message.failedundelivered · message.sent, message.faileddelivered · message.sent, message.deliveredcurl -X POST 'https://staging.useline.io/api/v1/sandbox/inbound' \
-H "Authorization: Bearer $LINE_API_KEY" \
-H 'Content-Type: application/json' \
-d '{
"from": "+447700900456",
"to": "+447700900123",
"body": "Thanks, see you then."
}'Authentication
SMS API access needs approval. An organisation owner or admin can request access under Settings → API. After approval, create a scoped test or live key there. Web Chat and lead forms use the widget public key under Settings → Webchat.
Send the key as a bearer token. The environment is fixed when the key is created.
Authorization: Bearer line_sk_…line_sk_test_…Sandbox records only. No carrier, no credits, no workflows.line_sk_live_…A live send needs a permitted sender, a recipient who has not opted out, enough wallet credits, and an Idempotency-Key.account.readmessages.sendmessages.readconversations.readphone_numbers.readusage.readwebhooks.manageevents.readtemplates.readtemplates.manageopt_outs.readopt_outs.managelookups.readWebhooks
Line posts each event as JSON to every active webhook that subscribes to its type. A webhook created with a test key receives only sandbox events, with livemode: false.
To verify a request, compute HMAC-SHA256 of {t}.{Line-Event-Id}.{raw body} with the whole signing secret. Accept the request if any v1 value matches and t is within five minutes. After a secret rotation, both secrets sign for 24 hours.
Return a 2xx status to confirm. A 4xx other than 408 or 429 stops delivery. Anything else retries with backoff from 30 seconds to one hour, up to 12 attempts within 72 hours. Use Line-Event-Id to ignore duplicates. A message body is included only when the key that last configured the webhook has messages.read.
import crypto from "node:crypto";
// rawBody is the exact request body as a string. Parse JSON after this check.
export function verifyLineWebhook(rawBody, headers, secret) {
const parts = (headers["line-signature"] ?? "").split(",");
const timestamp = Number(parts.find((p) => p.startsWith("t="))?.slice(2));
const signatures = parts.filter((p) => p.startsWith("v1=")).map((p) => p.slice(3));
if (!timestamp || Math.abs(Date.now() / 1000 - timestamp) > 300) return false;
const expected = crypto
.createHmac("sha256", secret)
.update(`${timestamp}.${headers["line-event-id"]}.${rawBody}`)
.digest("hex");
return signatures.some(
(signature) =>
signature.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected)),
);
}{
"id": "e7b2d9c4-5a1f-4e3b-8c6d-0f9a2b4e7c18",
"type": "message.delivered",
"api_version": "2026-09-20",
"created_at": "2026-10-09T09:41:07.214Z",
"livemode": false,
"data": {
"message_id": "6f1c2a9e-3b7d-4e2a-9c51-0d8e4f7a2b10",
"conversation_id": null,
"status": "delivered",
"reason": null,
"resource_version": 2,
"body": "Hi Sam, your engineer arrives between 10 and 12 tomorrow.",
"client_reference": null,
"metadata": {},
"sender_id": "0b6d1f3e-7a2c-4e58-9d14-3f8a6c2e1b90",
"sender_name_id": null
}
}Errors
Every SMS API error returns the same shape. Quote request_id when you contact support. Retry only when retryable is true.
400The request is invalid.401The API key is invalid, expired, or revoked.403API access is inactive or the key lacks a required scope.429A key or organisation rate limit was reached.503The operation is temporarily unavailable.{
"error": {
"code": "insufficient_credits",
"message": "The live wallet has insufficient available credits.",
"field": null,
"request_id": "req_6b1f0c2a",
"retryable": false
}
}Automated discovery
Line publishes an API catalog at the well-known URI defined by RFC 9727. It is an RFC 9264 linkset listing each API with its OpenAPI description, its documentation, and a health endpoint.
curl https://staging.useline.io/.well-known/api-catalog