v1 API reference
The canonical base URL is https://msghop.com. JSON endpoints are versioned beneath /v1; credentials are tenant- and app-scoped.
Common request headers
Authorization: Bearer <server-credential>— required for protected endpoints.Content-Type: application/json— required for message and OTP submission.Idempotency-Key— required for message and OTP submission.X-Request-Id— optional caller correlation ID, 2–128 safe characters. A generated ID is returned when omitted.
Protected responses use Cache-Control: no-store, X-Content-Type-Options: nosniff, and X-Request-Id.
Download the OpenAPI 3.1 contract or follow the agent integration guide for authentication and registration over HTTP.
Endpoints
Create or refresh a short-lived MessageHop user session.
Invoke an authorized Customer operation using a user access token and direct JSON.
Verify a server credential and return its effective tenant, app, environment, actions, and channels.
Durably accept one message for asynchronous provider dispatch. Requires messages.send and an allowed channel.
Expand bounded legacy universal toggles and recipient arrays into independently accepted, priced, idempotent messages. Returns 202 or item-level 207 results.
Send a server-generated phone OTP and verify its one-time challenge. Requires an approved mapped template and the same billing boundary as messages.
Read the caller-scoped message projection. Requires messages.read.
Read the Organization balance projection for one ISO-style uppercase currency. Requires billing.read.
Read bounded app-scoped financial activity. Requires usage.read.
Public operational release endpoints. They do not authorize customer messaging.
POST /v1/messages
Request body
{
"projectId": "project_checkout",
"appId": "app_server",
"channel": "sms",
"to": "+919876543210",
"templateId": "order_update",
"variables": {"order_id": "8472"},
"clientReference": "order-8472"
}projectId,appId, andtemplateId: 2–128 letters, digits, underscores, or hyphens.channel:sms,whatsapp, oremail, subject to credential and account activation.to: exactly one channel-valid recipient; SMS and WhatsApp use a normalized international number and legacy multi-recipient arrays are rejected.recipientName: optional bounded email recipient display name; sender domain/from/reply-to values come from the server-owned Registered App service configuration.variables: must contain every variable declared by the approved template, no extras, and at most 50 values up to 500 characters; WhatsApp values become server-ownedbody_Ncomponents.clientReference: optional customer reference up to 128 characters.
202 response
{
"messageId": "msg_...",
"state": "ACCEPTED",
"acceptedAt": "2026-07-26T12:00:00.000Z",
"estimatedCharge": {
"amountMinor": 27,
"currency": "INR",
"status": "provisional",
"priceVersionId": "sms_inr_..."
},
"requestId": "req_..."
}
POST /v1/messages/batch
Use the batch contract when migrating the legacy universal sender or a legacy WhatsApp multi-recipient call. Send one to fifty expanded messages in a single idempotent operation. The server accepts canonical items and the bounded legacy aliases, selects st, wt, or et per channel, and never uses body API keys or client provider components for authentication or routing. The response is 202 when all items are accepted/replayed and 207 when item-level policy or financial checks reject some items; malformed requests, missing templates/senders, or missing prices fail the batch before acceptance.
{
"messages": [{
"senderId": "LEGACY",
"r": ["+919876543210", "+919876543211"],
"t": {"s": true, "w": true, "e": false},
"st": "order_update",
"wt": "order_update_whatsapp",
"v": {"order_id": "8472"}
}]
}
POST /v1/otp
Send a server-generated SMS or WhatsApp OTP through a scoped Registered App credential. The response contains only the challenge and message identities; the code is never returned. The request is charged and accepted atomically with the message outbox.
{
"channel": "sms",
"to": "+919876543210",
"templateId": "login_otp",
"otpVariable": "otp",
"otpLength": 6,
"ttlSeconds": 300
}Supply an Idempotency-Key. Only an approved customer template, mapped sender, active credential scope, available price, balance, entitlement, and provider readiness can permit delivery.
POST /v1/otp/verify
{
"challengeId": "otp_...",
"code": "123456"
}Verification is one-time and rate-limited. A successful response is {"success":true,"verified":true}; invalid, expired, locked, cross-scope, and unknown challenges fail closed.
GET /v1/messages/{messageId}
A message ID begins with msg_. The response contains messageId, state, channel, acceptedAt, optional updatedAt, and a billing object with status, amountMinor, and currency. Unknown and cross-tenant IDs return a non-enumerating not-found result.
GET /v1/balances
Required query: currency, exactly three uppercase letters. The response includes Organization ID, currency, availableMinor, reservedMinor, postedMinor, asOf, projection version, reconciliation status, and request ID. These are projections; immutable ledger events remain authoritative.
GET /v1/usage
Required query fields are currency, from, and to. Times are epoch milliseconds, the range must stay within one UTC calendar month, cannot exceed 31 days, and cannot extend materially into the future. limit defaults to 50 and accepts 1–100. Pass the opaque nextCursor to retrieve the next page.
Each entry includes entry ID, type (reservation, settlement, or release), message ID, amount in minor units, status, occurrence time, and optional related-entry ID.
Money and time
All monetary amounts are integers in the currency's minor unit; never interpret them as floating-point major units. Times in JSON responses are ISO 8601 UTC strings, while usage query bounds are epoch milliseconds.