Send Message API
Submit outbound SMS messages using your project API key. The endpoint accepts a message submission, persists the logical message record, and queues delivery asynchronously.
Note: 202 Accepted means the platform queued the message — not that the carrier has delivered it. Current v1 limitation: API key auth is supported for message submission only. Read endpoints require Sanctum auth.
Endpoint
Send a POST request to a single path. The project context is supplied via the id field in the request body, not in the URL.
# Project context is passed via the request body id field
Full URL example
https://api.lomisend.com/api/v1/send-message
Authentication
Send the project API key in the X-API-Key header. Keys follow the format {prefix}_{type}_{64_hex_characters}.
sms_live_<64 hex>
Live key — real delivery
sms_test_<64 hex>
Test key — sandboxed
Content-Type: application/json
Accept: application/json
X-API-Key: sms_live_your_64_hex_character_key_here
X-Request-ID: your-request-id # optional but echoed in error responsesX-Request-ID is optional but useful — RFC 7807 error responses echo it back as request_id, making it easy to correlate failures in your logs.
Request body
JSON object with the following fields:
| Field | Type | Required | Notes |
|---|---|---|---|
id | string | Yes | Target project identifier (project UUID). Client assertion only — the backend verifies it against the authenticated API key. |
to | string | Yes | Recipient phone number in E.164 format, e.g. +251911234567 |
sender_id | string | Conditional | Sender ID value/name, not a UUID. For live keys, omit or null to use the project default. For test keys, required and must equal the platform test sender name. |
body | string | Yes | Message body, max 5000 characters |
id is a client assertion only. The backend compares it against the authenticated API key project and derives execution context from the key snapshot.
sender_id is matched by sender name/value. Legacy UUID-style sender payloads are rejected.
If a live-key request omits sender_id, the project must have an active default sender ID or the request fails with 422.
encoding and metadata must not be provided. Both fields are rejected if the client sends them.
Live key example
LIVEUse a live key after sender IDs, subscription access, and billing are ready.
curl -X POST "https://api.lomisend.com/api/v1/send-message" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "X-API-Key: sms_live_your_64_hex_character_key_here" \
-H "X-Request-ID: msg-001" \
-d '{
"id": "01926d3e-8f1c-7a2b-b8e5-3f4a5c6d7e8f",
"to": "+251911234567",
"sender_id": "MyBrand",
"body": "Hello from our platform!"
}'202 Accepted response
{
"data": {
"id": "01JRXXXXXXXXXXXXXXXXXXXXX",
"to_number": "+251911234567",
"sender_address": "MyBrand",
"body": "Hello from our platform!",
"segment_count": 1,
"attempt_count": 1,
"status": "pending",
"created_at": "2026-03-18T12:00:00+00:00",
"mode": "live",
"charged_from": "quota",
"cost_cents": 0
}
}Test key example
TESTTest keys use the same endpoint but are fully sandboxed. Use them while building your integration.
sender_id must equal the configured platform test sender name
to must already be approved for that project
Accepted responses return mode: "test"
No subscription or billing cycle required
curl -X POST "https://api.lomisend.com/api/v1/send-message" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "X-API-Key: sms_test_your_64_hex_character_key_here" \
-d '{
"id": "01926d3e-8f1c-7a2b-b8e5-3f4a5c6d7e8f",
"to": "+251911000001",
"sender_id": "PlatformTest",
"body": "Test mode send"
}'202 Accepted response
{
"data": {
"id": "01JRXXXXXXXXXXXXXXXXXXXXX",
"to_number": "+251911000001",
"sender_address": "PlatformTest",
"body": "Test mode send",
"segment_count": 1,
"attempt_count": 1,
"status": "pending",
"created_at": "2026-03-18T12:00:00+00:00",
"mode": "test",
"charged_from": "wallet",
"cost_cents": 7
}
}Response fields
Successful responses wrap the message object under data.
| Field | Type | Meaning |
|---|---|---|
id | string | Logical message UUID — your submission reference |
to_number | string | Recipient number exactly as stored |
sender_address | string | Sender address used for the message |
body | string | Message body |
segment_count | integer | Number of SMS segments computed for billing and delivery |
attempt_count | integer | Delivery attempt count. Starts at 1 |
status | string | Public message state. Immediately after acceptance typically pending |
created_at | string | ISO 8601 timestamp |
mode | string | live or test |
charged_from | string | null | Billing source — quota or wallet |
cost_cents | integer | Amount charged in cents for this accepted send |
Error handling
The API uses RFC 7807 problem responses. Handle errors by slug, not only by status code, as multiple slugs can share the same HTTP status.
{
"type": "https://api.lomisend.com/errors/validation-failed",
"title": "Validation Failed",
"status": 422,
"detail": "The given data was invalid.",
"errors": {
"to": ["A recipient phone number is required."]
},
"instance": "/api/v1/workspaces/.../projects/.../messages",
"request_id": "msg-001"
}| Status | Slug | When it happens |
|---|---|---|
| 401 | unauthenticated | No valid credentials were provided |
| 401 | invalid-api-key-format | X-API-Key does not match the expected format |
| 401 | invalid-api-key | Key was not found or was revoked |
| 401 | api-key-expired | Key expiry has passed |
| 401 | api-key-rotation-expired | Rotation overlap expired for an old key |
| 402 | insufficient-balance | No quota and no usable project wallet balance |
| 402 | no-billing-cycle | Live send has no open billing cycle |
| 403 | subscription-required | Live API key no longer has active subscription access |
| 403 | workspace-suspended | Workspace is suspended |
| 403 | api-key-insufficient-scope | Body id does not match the authenticated API key project, or the key has lost project scope |
| 422 | validation-failed | Invalid request body |
| 422 | test-sender-id-required | Test key omitted or misused sender_id |
| 422 | recipient-not-approved-for-test | Test key recipient is not approved for this project |
| 500 | invalid-overage-rate | Server-side billing configuration is invalid |
| 500 | platform-not-configured | Platform test sender is not configured correctly |
Validation rules
- ·
idis required and must be the target project UUID - ·
tomust be E.164 format — e.g.+251911234567 - ·
bodyis required - ·
sender_idfails when the value is unknown, inactive, belongs to another project, or no default sender is available - ·
encodingis rejected if the client provides it — encoding is resolved internally - ·
metadatais rejected if the client provides it — metadata is generated internally when needed
Live vs test keys
The same endpoint handles both key types. Behavior differs in several ways:
| Behavior | Live key | Test key |
|---|---|---|
| Requires active subscription | ✓ | — |
| Requires open billing cycle unless wallet can't cover test rate | ✓ | — |
| Sender resolution | Project sender or active default | Platform test sender only |
| Recipient restriction | Normal send rules | Must be approved for project |
| mode in response | live | test |
Use a test key while building your integration.
Move to a live key after sender IDs, subscription, and billing are ready.
Do not hardcode sender UUIDs — the contract expects sender names.
Integration notes
Checklist for a reliable integration:
Treat 202 as asynchronous acceptance only
The carrier has not delivered the message yet. Poll delivery status or use webhooks to track the outcome.
Persist the message id
Keep the returned message id on your side as your platform reference for support and correlation.
Log X-Request-ID
If you send X-Request-ID, keep it in your logs. It is echoed in RFC 7807 error responses as request_id.
Handle errors by slug
Match RFC 7807 errors on the slug field, not only HTTP status code. Multiple slugs share the same status.
Default sender for live traffic
If you omit sender_id for live requests, ensure the project has one active default sender configured or the request will fail with 422.
Ready to send your first message?
Create a free account, pick up your API key from the dashboard, and you can send your first SMS in minutes.