# Instasent - transactional-api (full documentation) > Autocontained dump of every page under /transactional-api. Paste this into an AI assistant or feed it to an agent as context for transactional-api integration work. Source: https://docs.instasent.com/ OpenAPI spec: https://docs.instasent.com/openapi/transactional.openapi.yaml --- URL: https://docs.instasent.com/transactional-api/overview # Overview The Transactional API is Instasent's SMS wholesale surface: single send, bulk send, delivery reports, inbound, HLR lookup, balance and pricing, over HTTP (REST) or SMPP. It is the evolution of the Legacy API — every new SMS integration should land here. The Transactional API — also known internally as the **SMS API** — is Instasent's core SMS surface. It connects your application directly to carrier networks, delivering to handsets in 200+ countries, and covers everything you need around raw SMS: sending (single or batched), receiving DLRs and inbound, querying message history, running HLR lookups and reading price profiles and account balance. It is the **evolution of the [Legacy API](/legacy-api/overview)** — same endpoint surface, modernised with DLR webhooks, per-project tokens, SMPP and the `api_sms` token model. If you are about to write new code, write it against this API. Two transports expose the same underlying pipeline. Pick the one that fits your stack — most teams start with HTTP and only move to SMPP when volume or session-state requirements justify it. - [HTTP (REST)](/transactional-api/http/quickstart) - JSON over HTTPS. Best for application servers, serverless functions and anything that already speaks REST. Webhook-based DLRs. - [SMPP](/transactional-api/smpp/integration) - Persistent TCP sessions, binary protocol. Best for high-throughput platforms (messaging providers, aggregators) that already operate an SMPP stack. ## What you can do - **Send SMS, one at a time** — `POST /sms` for triggered traffic (OTPs, alerts, receipts, notifications). - **Send SMS in bulk** — `POST /sms/bulk` takes a collection of messages in a single request. This is the endpoint for API-driven campaigns, marketing fan-outs and any high-volume dispatch. Customers including marketing agencies run entire campaigns on top of it. - **Receive delivery reports** (DLRs) as carriers report status back through the chain, pushed to your webhook in real time. - **Accept inbound messages** when the account has two-way enabled. - **Query message history** and per-message status — `GET /sms`, `GET /sms/{id}`. - **Run HLR / number lookups** — `POST /lookup`, `GET /lookup/{id}` and `GET /lookup` to check handset presence and routing before sending. - **Read account balance** — `GET /organization/account`. - **Read price profiles** — `GET /sms/price-profile/me/countries` and `GET /lookup/price-profile/me/countries` for per-country pricing. ## When to use this API Reach for Transactional whenever you need raw SMS capacity — single, batched, or wholesale — without asking Instasent to manage the recipient audience. Typical workloads: - **User-triggered messages** — OTPs, password resets, receipts, shipping notifications. One message per event, delivered in seconds, via `POST /sms`. - **API-driven campaigns and bulk dispatch** — marketing sends, notification fan-outs, anything that fires many messages in one go. Use `POST /sms/bulk`. - **SMS wholesale** — aggregators, messaging platforms and resellers pushing their own traffic through Instasent. Use HTTP at moderate rates; use [SMPP](/transactional-api/smpp/integration) for carrier-grade throughput with persistent sessions. - **Pre-send validation and pricing** — HLR lookup and price profiles to route and cost-check before dispatch. If you need Instasent to own the audience, personalization, segmentation and attribution, use the [Product API](/product-api/guide) instead — it delegates delivery to this one but adds the customer-data layer on top. The Transactional API is the right pick when the caller already manages its recipient list and just needs the full SMS pipe. ## What to read next - [HTTP Quickstart](/transactional-api/http/quickstart) - Send your first SMS over REST in under five minutes. - [Bulk sending](/transactional-api/http/bulk) - Up to 100 messages per request, queue-based dispatch, the endpoint for API-driven campaigns. - [HTTP Authentication](/transactional-api/http/authentication) - Token types, header vs. query string, rotation. - [Receiving DLRs](/transactional-api/http/dlrs) - Webhook payload, status list, two-way inbound. - [API Reference](/transactional-api/http/reference) - Every endpoint, every parameter. --- URL: https://docs.instasent.com/transactional-api/http/quickstart # Quickstart Send your first SMS over the Transactional API in under five minutes. Grab a token, fire one HTTP request, and point a webhook at the DLR URL to see delivery. The Transactional API over HTTP needs three things: an account, an `api_sms` token and one POST request. This walkthrough takes you from zero to a delivered message. If you already know you need high throughput or API-driven campaigns, jump straight to [Bulk sending](/transactional-api/http/bulk) — same token, one call, up to 100 messages per request. #### 1. Create an Instasent account Sign up at [instasent.com](https://instasent.com) and complete onboarding. A Transactional API token is issued automatically for your first project. #### 2. Grab your token In the dashboard, open **API tokens** and copy the `api_sms` token. Treat it as a secret — never commit it to version control. #### 3. Send your first message #### curl ```bash curl -X POST https://api.instasent.com/transactional/v1/sms \ -H "Authorization: Bearer $INSTASENT_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "from": "Instasent", "to": "+34600000000", "text": "Hello from Instasent" }' ``` #### node ```js const res = await fetch("https://api.instasent.com/transactional/v1/sms", { method: "POST", headers: { Authorization: `Bearer ${process.env.INSTASENT_TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify({ from: "Instasent", to: "+34600000000", text: "Hello from Instasent", }), }); const sms = await res.json(); ``` #### python ```python import os, requests res = requests.post( "https://api.instasent.com/transactional/v1/sms", headers={"Authorization": f"Bearer {os.environ['INSTASENT_TOKEN']}"}, json={ "from": "Instasent", "to": "+34600000000", "text": "Hello from Instasent", }, ) sms = res.json() ``` #### 4. Check delivery The response contains an `id`. Point your webhook endpoint at the DLR URL in your project settings — Instasent will `POST` delivery updates as the message progresses through the carrier network. See [Receiving DLRs](/transactional-api/http/dlrs) for payload details. ## Sending many messages at once For API-driven campaigns, notification fan-outs or any high-volume dispatch, switch from `POST /sms` to `POST /sms/bulk` — a single call that carries up to 100 messages, returns accepted and rejected items side by side, and queues the accepted ones on the platform for fastest-possible dispatch. The same `api_sms` token works. ```bash curl -X POST https://api.instasent.com/transactional/v1/sms/bulk \ -H "Authorization: Bearer $INSTASENT_TOKEN" \ -H "Content-Type: application/json" \ -d '[ { "from": "Instasent", "to": "+34600000001", "text": "Hello one" }, { "from": "Instasent", "to": "+34600000002", "text": "Hello two" } ]' ``` Full request and response shape, per-request limits, throughput model and partial-success handling live in [Bulk sending](/transactional-api/http/bulk). ## What's next - **[Bulk sending](/transactional-api/http/bulk)** — `POST /sms/bulk`, 100 messages per request, queue-based throughput. The endpoint to use for campaigns. - **[Authentication](/transactional-api/http/authentication)** — token scopes, rotation, header vs. query-string. - **[Rate limits](/transactional-api/http/rate-limits)** — per-endpoint limits and how to read the `X-RateLimit-*` headers. One bulk call counts as one request against the window. - **[Errors](/transactional-api/http/errors)** — status codes, error payload shape, retry guidance. - **[Receiving DLRs](/transactional-api/http/dlrs)** — webhook payload and status list. - **[Reference](/transactional-api/http/reference)** — every endpoint, every parameter. --- URL: https://docs.instasent.com/transactional-api/http/bulk # Bulk sending Send many SMS in a single request via POST /sms/bulk — the Transactional API's throughput endpoint for campaigns, notification fan-outs and wholesale dispatch. Covers the request and response shape, per-request limits, the queue-based throughput model, partial-success handling and when to prefer SMPP. The maximum throughput you can get out of the Transactional API over HTTP is through bulk. One `POST /sms/bulk` request carries up to 100 messages; Instasent validates each item, queues the accepted ones on the platform and dispatches them to the carrier network as fast as the routes allow. From the caller's point of view the HTTP request returns as soon as the batch is staged — the delivery loop is ours to run. That is why an API-driven campaign sent through bulk will out-perform the same campaign fired as thousands of individual `POST /sms` calls, regardless of how much concurrency the caller puts on the client side. This page covers the request and response shape, the per-request limits, how the queue behaves under load, DLRs, partial-success handling, and when to reach for SMPP instead. ## How a bulk request flows ``` Your backend ──POST /sms/bulk──▶ Instasent API ──validate──▶ queue ──▶ carrier ──▶ handset ◀─── 201 {entities, errors} ─── │ ▼ Your webhook ◀────────────────────── DLR per message ──────────────────────────────── carrier ``` - **Submit** a single `POST` with an array of up to 100 SMS items. - **Validate**: the server checks each item independently. Valid ones are accepted; invalid ones are rejected with field-level errors, and the rest of the batch still goes through. - **Queue**: accepted messages are staged on the platform's dispatch queue. The HTTP request returns immediately with the list of `entities` (accepted, each with an `id`) and the list of `errors` (rejected, with per-field messages). It does **not** block on carrier delivery. - **Dispatch**: the platform drains the queue concurrently against the carrier routes. Throughput is bounded by route capacity, not by your HTTP client. - **DLR**: each accepted message produces its own DLR webhook events, exactly like a single send. Match them by `id`. The practical consequence for campaigns: submit as many full batches of 100 as you can afford on your rate-limit window; the queue absorbs the burst and drains at carrier speed. Trying to match that throughput through `POST /sms` one at a time will hit the rate-limit wall long before the queue does. ## Request Send a `POST` to `/sms/bulk` with an `api_sms` bearer token and a JSON **array** of items as the body — there is no wrapper object. #### curl ```bash curl -X POST https://api.instasent.com/transactional/v1/sms/bulk \ -H "Authorization: Bearer $INSTASENT_TOKEN" \ -H "Content-Type: application/json" \ -d '[ { "from": "Instasent", "to": "+34600000001", "text": "Hello one" }, { "from": "Instasent", "to": "+34600000002", "text": "Hello two" }, { "from": "Instasent", "to": "+34600000003", "text": "Hello three" } ]' ``` #### node ```js const messages = [ { from: "Instasent", to: "+34600000001", text: "Hello one" }, { from: "Instasent", to: "+34600000002", text: "Hello two" }, { from: "Instasent", to: "+34600000003", text: "Hello three" }, ]; const res = await fetch("https://api.instasent.com/transactional/v1/sms/bulk", { method: "POST", headers: { Authorization: `Bearer ${process.env.INSTASENT_TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify(messages), }); const { entities, errors } = await res.json(); ``` #### python ```python import os, requests messages = [ {"from": "Instasent", "to": "+34600000001", "text": "Hello one"}, {"from": "Instasent", "to": "+34600000002", "text": "Hello two"}, {"from": "Instasent", "to": "+34600000003", "text": "Hello three"}, ] res = requests.post( "https://api.instasent.com/transactional/v1/sms/bulk", headers={"Authorization": f"Bearer {os.environ['INSTASENT_TOKEN']}"}, json=messages, ) data = res.json() ``` Each item accepts the same fields as `POST /sms`: | Field | Required | Notes | | -------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | `from` | yes | Sender ID. 3–14 characters — up to 11 alphanumeric chars or up to 14 digits. | | `to` | yes | Destination MSISDN in E.164 format (e.g. `+34600000000`). | | `text` | yes | Message body. | | `allowUnicode` | no | Defaults to `false`. Set to `true` if the text may contain non-GSM-7 characters. | | `clientId` | no | Your own reference (≤40 chars). Must be unique per SMS. Echoed back on the response and on DLRs — use it to correlate without storing Instasent's `id`. | ## Response `201 Created` with a JSON object containing two arrays: ```json { "entities": [ { "id": "...", "clientId": null, "from": "Instasent", "to": "+34600000001", "text": "Hello one", "status": "...", "..." : "..." } ], "errors": [ { "fields": { "to": ["This value is not valid"] } } ] } ``` - `entities` — one object per **accepted** message, with its own Instasent `id`. Track that `id` (or the `clientId` you sent) against the DLRs you will receive. - `errors` — one object per **rejected** item, with per-field error messages under `fields`. The order mirrors the input array position of the rejected items. A 201 means the batch was processed — not that every item was accepted. Always inspect `errors` before assuming the whole batch went through. ## Limits per request | Limit | Value | What happens when you exceed it | | ---------------------- | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Items per request | **1 – 100** | `413 Request Entity Too Large` or `422` — split the batch and retry. | | Per-item validation | See field table above | The invalid item lands in `errors`; the rest of the batch still goes through. | | Per-account rate limit | See [Rate limits](/transactional-api/http/rate-limits) | `429 Too Many Requests`. One bulk call counts as **one** request against the window — that is what makes bulk so much more rate-limit-efficient than looping over `POST /sms`. | If you need to dispatch more than 100 messages, split the payload across multiple bulk requests and fire them concurrently. Throughput scales linearly with the number of parallel batches until you reach your account's rate limit. > **Tip**: Batching only makes the HTTP side efficient. The dispatch queue is shared across all your traffic — so once your batches are in the queue, there is no ordering guarantee between them. If two messages in the same batch must go out in a fixed order, send them as separate calls. ## Throughput, queueing and back-pressure The dispatch queue is sized to absorb large campaigns. In practice this means: - **Bursts are absorbed**. A sudden wave of bulk submissions does not translate into rejections downstream — messages wait in the queue and drain as the carrier routes accept them. - **Throughput is carrier-bound**, not HTTP-bound. The limiting factor is the route capacity and the destination country, not your HTTP client's concurrency. - **Higher ceilings are a conversation**. If the default rate limit gets in the way of a campaign's peak, open a ticket with the expected volume and we will raise the window on your account. > **Tip**: For sustained carrier-grade throughput (aggregators, messaging platforms, resellers), consider [SMPP](/transactional-api/smpp/integration) instead. Same underlying pipeline, persistent TCP sessions, binary protocol — no HTTP overhead per message. ## Delivery reports DLRs work the same as for a single send: one webhook POST per status transition, keyed by the message `id`. See [Receiving DLRs](/transactional-api/http/dlrs) for the payload shape, the status list and how the same message can produce multiple DLRs during its lifecycle. If you sent a `clientId`, it is echoed on every DLR for that message — useful when you would rather not store Instasent's `id` on your side. ## Handling partial failures A pragmatic pattern in your caller: #### 1. Build the batch with your own correlation id Include `clientId` on every item so you can match responses and DLRs back to records on your side without an extra lookup. #### 2. Fire the bulk request One HTTP call per batch of up to 100 messages. Check the HTTP status — `201` is the happy path. #### 3. Reconcile entities and errors Iterate both arrays. For each item in `entities`, record the returned `id` (or your `clientId`) and mark the message as accepted. For each item in `errors`, log the per-field message and retry only if it is a transient issue. #### 4. Process DLRs asynchronously Delivery status arrives on the webhook, not on the HTTP response. Update the final state as DLRs come in. ## HTTP bulk vs SMPP | | HTTP `/sms/bulk` | SMPP | | -------------------- | ---------------------------------------------------------------------------------- | -------------------------------------------------------------------- | | Setup cost | `api_sms` token + one HTTP call | Persistent TCP session, binary protocol, more ops overhead | | Best for | API-driven campaigns, notification fan-outs, periodic dispatch, embedded use cases | Aggregators, messaging platforms, carrier-grade sustained throughput | | Per-message overhead | HTTP request / JSON | Binary PDU on an open session | | Throughput ceiling | Bounded by rate limit on your account; excellent for bursts | Highest — designed for continuous streaming | | Engineering lift | Low | Higher (session management, retries, pluggable TLVs) | Start on HTTP bulk. Move to SMPP only when the sustained traffic profile — not a one-off burst — justifies the extra engineering lift. ## Pitfalls - **Wrapping the body in a `messages` object.** The body is a JSON array at the top level. Wrapping it breaks validation. - **Treating 201 as "all accepted".** Always iterate `errors` — per-item rejections live there, not in the HTTP status. - **Parallel `POST /sms` instead of bulk.** You will burn the rate-limit window without raising the ceiling. The queue is what gives bulk its throughput, not HTTP concurrency. - **Forgetting `clientId`.** Without it you have to store Instasent's `id` to correlate DLRs — an avoidable dependency. - **Batching items that need strict ordering.** The queue does not preserve submission order across the platform. If ordering matters between two messages, send them separately. ## What's next - **[Receiving DLRs](/transactional-api/http/dlrs)** — webhook payload, status list, per-message matching. - **[Rate limits](/transactional-api/http/rate-limits)** — how the window is counted and how to ask for more. - **[Errors](/transactional-api/http/errors)** — status codes and retry guidance, including `413` and `422`. - **[SMPP integration](/transactional-api/smpp/integration)** — the next step up when HTTP is not enough. - **[API Reference](/transactional-api/http/reference)** — full shape of `POST /sms/bulk` and every other endpoint. --- URL: https://docs.instasent.com/transactional-api/http/authentication # Authentication The Transactional API authenticates each request with a bearer token. Create it in the dashboard, send it in the Authorization header, and rotate it when it leaks. Every request to the Transactional API carries a token. Tokens are issued in the dashboard, scoped per project, and passed either in the `Authorization` header (recommended) or as a query-string parameter (convenient for quick tests). ## Getting a token Sign in to the [Instasent dashboard](https://dashboard.instasent.com), open **API tokens** and create an `api_sms` token for the project that will send the traffic. Copy the value straight away — it is shown once. > **Warning**: Treat tokens as production secrets. Keep them in an environment variable or a secrets manager; never commit them to the repo or embed them in client-side code. ## Sending the token ### In the `Authorization` header Preferred in every environment. The token never appears in URLs, logs or the browser history. ```bash curl https://api.instasent.com/transactional/v1/sms \ -H "Authorization: Bearer $INSTASENT_TOKEN" ``` ### As a query-string parameter Convenient for one-off checks from a browser or a copy-pasted curl. Only use it from trusted shells — URLs are logged by proxies and CDNs. ```bash curl "https://api.instasent.com/transactional/v1/sms?access_token=$INSTASENT_TOKEN" ``` ## Rotating a token Tokens do not expire. Rotate them whenever a member of the team leaves, whenever a secret might have been exposed, and at least once a year as a hygiene measure. #### 1. Issue the replacement Create a new `api_sms` token in the dashboard **before** revoking the old one. This keeps traffic flowing while you redeploy. #### 2. Roll the new token out Update your secrets store and redeploy the workers that call the API. #### 3. Revoke the old token Once the replacement is live everywhere, delete the old token in the dashboard. Any request still using it will fail with `401 Unauthorized`. ## What's next - **[Rate limits](/transactional-api/http/rate-limits)** — how many requests per minute and what the `X-RateLimit-*` headers tell you. - **[Errors](/transactional-api/http/errors)** — status codes returned by the API and how to retry safely. --- URL: https://docs.instasent.com/transactional-api/http/rate-limits # Rate limits Transactional HTTP traffic is rate-limited per organization and per endpoint. Each response reports the current window through X-RateLimit headers, and breaching the limit returns 429. Every Transactional endpoint enforces a request-per-minute ceiling, scoped by organization and by endpoint. Limits are generous by default and documented per endpoint in the [API Reference](/transactional-api/http/reference) — if you need more throughput on a specific endpoint, open a ticket with the expected peak rate and we will raise the ceiling on your account. ## Reading the headers Every response includes three headers with the current window state. Log them in production — they are the cheapest way to spot a client that is about to hit the wall. ``` X-RateLimit-Limit 1200 X-RateLimit-Remaining 1195 X-RateLimit-Reset 1893452400 ``` | Header | Meaning | | ----------------------- | --------------------------------------------- | | `X-RateLimit-Limit` | Total requests allowed in the current window. | | `X-RateLimit-Remaining` | Requests left before the window tightens. | | `X-RateLimit-Reset` | Unix timestamp when the counter resets. | ## When you hit the limit Requests that exceed the window return **`429 Too Many Requests`**. The body is empty; the `X-RateLimit-Reset` header tells you when to retry. > **Tip**: Back off exponentially rather than retrying in a tight loop. A client that hammers a 429 response keeps the window full and never recovers — waiting until `X-RateLimit-Reset` resolves the situation cleanly. ## What's next - **[Errors](/transactional-api/http/errors)** — every status code you might receive, including `429`. - **[API Reference](/transactional-api/http/reference)** — per-endpoint documentation. --- URL: https://docs.instasent.com/transactional-api/http/errors # Errors The Transactional HTTP API uses standard HTTP status codes. This page lists the ones you will encounter, what they mean and whether a retry is safe. Every response carries a meaningful HTTP status code. A `2xx` means the request was accepted; anything else signals a problem your code should handle explicitly. Error bodies, when present, are JSON with a `message` field describing the failure. ## Status codes ### `400 Bad Request` The request body is malformed or has the wrong shape. Not retryable — fix the payload first. ```http HTTP/1.1 400 Bad Request ``` ```json { "message": "Problems parsing JSON" } ``` ```json { "message": "Body should be a JSON object" } ``` ### `401 Unauthorized` The token is missing, revoked or malformed. Not retryable with the same token — check the [Authentication](/transactional-api/http/authentication) guide. ```http HTTP/1.1 401 Unauthorized ``` ### `402 Payment Required` The account has run out of balance. Not retryable until funds are topped up in the dashboard. ```http HTTP/1.1 402 Payment Required ``` ### `404 Not Found` The resource (message id, token id, project) does not exist or is not visible to the current token. ```http HTTP/1.1 404 Not Found ``` ### `413 Request Too Large` The request body exceeds the maximum accepted size. Not retryable — trim the payload. ```http HTTP/1.1 413 Request Too Large ``` ### `429 Too Many Requests` You hit the per-endpoint rate limit. Retry after `X-RateLimit-Reset`. See [Rate limits](/transactional-api/http/rate-limits). ```http HTTP/1.1 429 Too Many Requests ``` ### `500 Internal Server Error` Something broke on our side. Retry with exponential backoff; if the failure persists, contact support with the request id. ```http HTTP/1.1 500 Internal Server Error ``` ## Retry policy > **Tip**: Retry `429` and `5xx` with exponential backoff, starting at 1 s and capping at a minute or so. Everything in the `4xx` range other than `429` means the request itself is wrong — retrying will not help. ## What's next - **[Receiving DLRs](/transactional-api/http/dlrs)** — delivery reporting for messages that did leave the API successfully. - **[API Reference](/transactional-api/http/reference)** — per-endpoint responses. --- URL: https://docs.instasent.com/transactional-api/http/dlrs # Receiving DLRs Delivery reports (DLRs) are POSTed to a webhook URL configured per API token. Each call describes one status transition — sent, delivered, failed, inbound — with a machine-readable status and code. A DLR (Delivery Receipt) is how carriers tell you what happened to a message after it left Instasent. We aggregate the updates from every hop in the chain and forward each transition to a webhook you control. If two-way messaging is enabled on your account, inbound messages arrive on the same endpoint. ## Configuring the webhook Webhooks are configured per `api_sms` token in the dashboard. Point it at a public HTTPS endpoint that returns `2xx` quickly — we consider any non-2xx a failure and retry with exponential backoff. > **Warning**: Respond in under 5 seconds with a `2xx` and process the payload asynchronously. Long-running handlers trigger timeouts, retries and duplicate deliveries. ## Payload shape Every call is a `POST` with a JSON body: ```json { "id": "sms-id", "clientId": "custom-id", "status": "delivered", "code": 0, "eventAt": "2026-04-21T10:15:00Z" } ``` | Field | Description | | ---------- | ---------------------------------------------------------------------------------------------------------------- | | `id` | The Instasent message id. Matches the `id` returned when the SMS was created. | | `clientId` | The `clientId` you supplied when creating the message, if any — useful for correlation against your own records. | | `status` | Machine-readable status. See the list below. | | `code` | Numeric code that narrows down the reason for `error`/`failed` statuses. | | `eventAt` | ISO-8601 timestamp of the event. | When two-way is enabled on the account, the payload also carries a `message` field with the inbound text. ## Status list | Status | Meaning | | ----------- | ------------------------------------------------------------ | | `sent` | Message was sent to the device. | | `accepted` | Message was accepted by the carrier. | | `buffered` | Message is buffered by the carrier, waiting to be delivered. | | `delivered` | Message was delivered to the handset. | | `error` | Message could not be sent to the device. | | `failed` | Message could not be delivered. | | `expired` | Message expired before delivery was possible. | | `canceled` | Message was canceled. | | `rejected` | Message was rejected by the carrier. | | `unknown` | An unknown error occurred. | | `stop` | An opt-out inbound message was received from the handset. | | `inbound` | An inbound message was received (two-way only). | ## Code list Codes narrow down the reason for non-delivery. `0` is the happy path; everything else describes a specific failure class. | Code | Meaning | | ----- | ------------------ | | `0` | OK | | `1` | Unknown | | `2` | Absent temporarily | | `3` | Absent permanently | | `4` | Blocked subscriber | | `5` | Portability error | | `6` | Antispam reject | | `7` | Line busy | | `8` | Network error | | `9` | Illegal number | | `10` | Invalid message | | `11` | Unroutable | | `12` | Unreachable | | `13` | Age restriction | | `14` | Blocked carrier | | `15` | Insufficient funds | | `16` | Flooded | | `99` | Unknown error | | `100` | Reject | ## Idempotency The same message may generate multiple DLRs — typically `sent` → `buffered` → `delivered`. Use `id` as the primary key and treat each DLR as a status transition, not as the final state. > **Tip**: Persist the latest `status` and `eventAt` per `id` rather than appending every event. If your pipeline needs the full audit trail, log the raw payload to a separate store. ## What's next - **[Errors](/transactional-api/http/errors)** — for failures that happen before the message even leaves the API. - **[API Reference](/transactional-api/http/reference)** — retrieve the current status of a message on demand. --- URL: https://docs.instasent.com/transactional-api/smpp/authentication # Authentication The Transactional API over SMPP authenticates each session with a system_id and password carried on the bind PDU. Credentials are issued in the dashboard once SMPP is enabled on the account. SMPP authentication is per-session, not per-request. The client binds once, authenticates with `system_id` + `password`, and keeps the TCP connection open for the rest of the traffic window. ## Enabling SMPP on your account Open a ticket to have SMPP enabled on your organization. Once it's on, every `api_sms` token in the dashboard exposes an extra pair of credentials — `systemID` and `password` — for use over SMPP. The HTTP token remains usable in parallel. ## Server endpoint | Field | Value | | ----------------- | -------------------------------------------------------------------------------------------------- | | Host | `smpp.instasent.com` | | Port | `2775` | | Protocol versions | [SMPP v3.4](https://smpp.org/SMPP_v3_4_Issue1_2.pdf) and [SMPP v5.0](https://smpp.org/SMPP_v5.pdf) | ## Binding Use `bind_transceiver`, `bind_transmitter` or `bind_receiver` depending on the direction of traffic you need (see [Integration](/transactional-api/smpp/integration)). The required fields are the same on all three. | Field | Description | | ----------- | ----------------------------------------------------------- | | `system_id` | SMPP username, as shown next to the token in the dashboard. | | `password` | SMPP password, paired with the `system_id`. | Example PDU: ```json { "command": "bind_transceiver", "command_id": 9, "system_id": "myuser", "password": "mypass" } ``` > **Tip**: Keep sessions long-lived and issue `enquire_link` PDUs periodically to avoid idle disconnects from intermediate firewalls. Re-binding on every message defeats the point of SMPP. ## What's next - **[Integration](/transactional-api/smpp/integration)** — PDUs supported, sending, concatenation, DLRs, inbound messages. --- URL: https://docs.instasent.com/transactional-api/smpp/integration # Integration Supported PDUs, submit_sm fields, message concatenation, DLRs over deliver_sm and inbound handling for the Transactional API over SMPP. Once the session is bound (see [Authentication](/transactional-api/smpp/authentication)), traffic flows through a small set of PDUs. This page documents what is supported and how the payload is shaped. ## Supported PDUs | PDU | Purpose | | ------------------ | ----------------------------------------- | | `bind_transceiver` | Send and receive SMS on the same session. | | `bind_transmitter` | Send SMS only. | | `bind_receiver` | Receive SMS only. | | `submit_sm` | Send a mobile-terminated (MT) message. | | `deliver_sm` | Receive DLRs and inbound messages. | | `enquire_link` | Keep the session alive. | | `unbind` | Log out — SMPP v5.0 only. | ## Sending messages Use `submit_sm` to send an MT. The fields below are recognised; everything else is ignored or rejected depending on the PDU version. | Field | Description | Notes | | --------------------- | ------------------------------------------------- | ------------------------------------------ | | `source_addr` | Sender (from). | 3–11 chars. | | `destination_addr` | Recipient MSISDN. | E.164 with country prefix. | | `esm_class` | Message mode and type. | See concatenation below. | | `registered_delivery` | Whether to request DLRs. | `1` to receive DLRs. | | `data_coding` | Character encoding. | See table below. | | `short_message` | Message body. | Up to the encoding-dependent length limit. | | `message_payload` | Alternative to `short_message` for long messages. | See concatenation. | Example PDU: ```json { "command": "submit_sm", "command_id": 4, "source_addr": "INSTASENT", "destination_addr": "+34676388300", "esm_class": 0, "registered_delivery": 1, "short_message": "example of message" } ``` ### Data coding | Value | Encoding | | ----- | -------------------- | | `0` | GSM7 | | `3` | ISO-8859-15 (LATIN9) | | `8` | UTF-16BE (UCS2) | ## Message concatenation Messages that exceed the single-segment limit can be delivered in two ways. Pick whichever fits your SMPP client. ### `message_payload` Put the whole text in `message_payload` instead of `short_message`. The server segments for you. ```json { "command": "submit_sm", "source_addr": "INSTASENT", "destination_addr": "+34666000000", "esm_class": 0, "registered_delivery": 1, "message_payload": "Y, viéndole don Quijote de aquella manera, con muestras de tanta tristeza, le dijo: Sábete, Sancho, que no es un hombre más que otro si no hace más que otro." } ``` ### UDHI segmentation Set the User Data Header Indicator on `esm_class` and build the concatenation header yourself. ``` esm_class = 0x40 ``` The UDH prefix is inserted at the start of the message body: | Byte | Description | | ---- | ------------------------------------------------------------------- | | `05` | Length of UDH (5 bytes). | | `00` | Indicator for concatenated message. | | `03` | Subheader length (3 bytes). | | `XX` | Message identifier — any hex value, must match across all segments. | | `YY` | Total number of segments. | | `ZZ` | Sequence number of this segment. | Two-segment example: ``` # segment 1 esm_class = 0x40 short_message = 0x05 0x00 0x03 0x05 0x02 0x01 Y, viéndole don Quijote de aquella manera, con muestras de tanta tristeza, le dijo: Sábete, Sancho, que no es un hombre más que # segment 2 esm_class = 0x40 short_message = 0x05 0x00 0x03 0x05 0x02 0x02 otro. Todas estas borrascas que nos suceden son señales de que presto ha de serenar el tiempo y han de sucedernos bien las cosas ``` ## Receiving DLRs DLRs come in through `deliver_sm` PDUs with `esm_class=4`. The body is a single line in the usual SMPP DLR format: ``` id:288230378411154593 sub:001 dlvrd:001 submit date:1602260445 done date:1602260345 stat:DELIVRD err:000 text:none ``` | Field | Description | | ------------- | --------------------------------- | | `id` | Instasent message id. | | `sub` | Unused. | | `dlvrd` | `1` delivered, `0` not delivered. | | `submit date` | Send date, `YYMMDDHHMM`. | | `done date` | Completion date, `YYMMDDHHMM`. | | `stat` | Status — see table below. | | `err` | Error code. | ### DLR statuses | Status | Description | | --------- | --------------------------------- | | `DELIVRD` | Message delivered to destination. | | `EXPIRED` | Message validity period expired. | | `DELETED` | Message deleted. | | `ACCEPTD` | Message accepted. | | `UNDELIV` | Message undeliverable. | | `UNKNOWN` | Message in an invalid state. | | `REJECTD` | Message rejected. | ## Receiving inbound messages Inbound (MO) messages arrive through `deliver_sm` with `esm_class=0`. The content is carried on `short_message.message`. --- URL: https://docs.instasent.com/transactional-api/sdks/node # Node.js SDK Official Node.js client for the Instasent Transactional API. Install via npm, instantiate the SMS client with your api_sms token, and call the high-level methods instead of wiring fetch by hand. The Node.js SDK wraps the Transactional HTTP endpoints so you do not have to build requests manually. If the SDK does not cover a call you need, drop back to plain `fetch` — the [Reference](/transactional-api/http/reference) describes every endpoint. > **Warning**: The canonical source for this SDK is under review. Until it is confirmed, treat the method names below as indicative and double-check against the [GitHub repository](https://github.com/instasent) before wiring it into production. ## Installation ```bash npm install instasent ``` ## Send an SMS ```js const Instasent = require("instasent"); const client = new Instasent.SmsClient(process.env.INSTASENT_TOKEN); const response = await client.sendSms("My company", "+34666666666", "test message"); console.log(response.response_code); console.log(response.response_body); ``` ## Available methods ```js client.sendSms(sender, to, text); client.getSms(page, perPage); client.getSmsById(messageId); ``` ## Getting help For help installing or using the library, contact [support@instasent.com](mailto:support@instasent.com). Bugs and feature requests belong on the library's GitHub repository. --- URL: https://docs.instasent.com/transactional-api/sdks/php # PHP SDK Official PHP client for the Instasent Transactional API. Install via Composer, construct the SmsClient with your api_sms token, and call sendSms — Unicode, lookup and verify workflows are covered by separate clients in the same package. The PHP SDK wraps SMS, lookup and verify into small focused clients. Most integrations only need `SmsClient`. ## Installation ### Via Composer ```bash composer require instasent/instasent-php-lib ``` ### Via ZIP Download the source from [GitHub](https://github.com/instasent/instasent-php-lib/zipball/master), drop the folder into your project and require the files directly: ```php require_once __DIR__ . '/path/to/lib/Abstracts/InstasentClient.php'; require_once __DIR__ . '/path/to/lib/SmsClient.php'; ``` ## Send an SMS ```php sendSms('test', '+34647000000', 'test message'); echo $response['response_code']; echo $response['response_body']; ``` ### Unicode For SMS containing characters outside GSM-7 (accents, emoji) use `sendUnicodeSms`: ```php $response = $client->sendUnicodeSms('test', '+34647000000', 'Unicode test: ña éáíóú 😀'); ``` ## Retrieve an SMS ```php getSmsById('smsId'); echo $response['response_code']; echo $response['response_body']; ``` ## Lookup ```php doLookup('+34666000000'); echo $response['response_code']; echo $response['response_body']; ``` ## Verify Verify is a two-step workflow: request a code, then check it against the user input. ### Request a code ```php requestVerify('test', '+34647000000', 'Your code is %token%', 6, 300); echo $response['response_code']; echo $response['response_body']; ``` The fourth argument is the code length, the fifth is the validity in seconds. ### Check the code ```php $client = new Instasent\VerifyClient(getenv('INSTASENT_TOKEN')); $response = $client->checkVerify($requestVerifyId, $token); $body = json_decode($response['response_body']); if ($body->entity->status === 'verified') { // success } else { // handle pending / failed / expired } ``` ## Requirements PHP ≥ 5.2.3. ## Getting help For help installing or using the library, contact [support@instasent.com](mailto:support@instasent.com). Bugs and feature requests belong on the [GitHub repository](https://github.com/instasent/instasent-php-lib). --- URL: https://docs.instasent.com/transactional-api/sdks/python # Python SDK Official Python client for the Instasent Transactional API. Install via pip, instantiate the client with your api_sms token, and call send_sms. Minimal client over the Transactional HTTP endpoints. Anything beyond the methods below is a plain REST call — the [Reference](/transactional-api/http/reference) covers the whole surface. ## Installation ### Via pip ```bash pip install instasent ``` ### From source ```bash python setup.py install ``` ## Send an SMS ```python import os import instasent client = instasent.Client(os.environ["INSTASENT_TOKEN"]) response = client.send_sms("My company", "+34666666666", "test message") print(response["response_code"]) print(response["response_body"]) ``` ## Available methods ```python client.send_sms(sender, to, text) client.get_sms(page, per_page) client.get_sms_by_id(message_id) ``` ## Getting help For help installing or using the library, contact [support@instasent.com](mailto:support@instasent.com). Bugs and feature requests belong on the [GitHub repository](https://github.com/instasent). --- URL: https://docs.instasent.com/transactional-api/sdks/ruby # Ruby SDK Official Ruby client for the Instasent Transactional API. Install the instasent gem, instantiate Instasent::Client with your api_sms token, and call send_sms. Minimal client over the Transactional HTTP endpoints. For anything outside the methods below, drop back to a plain HTTP call — the [Reference](/transactional-api/http/reference) documents every endpoint. ## Installation ### Via RubyGems ```bash gem install instasent ``` ### Via Bundler Add the gem to your `Gemfile`: ```ruby gem "instasent" ``` Then run `bundle install`. ## Send an SMS ```ruby require "instasent" client = Instasent::Client.new(ENV["INSTASENT_TOKEN"]) response = client.send_sms("My company", "+34666666666", "test message") puts response["response_code"] puts response["response_body"] ``` ## Available methods ```ruby client.send_sms(sender, to, text) client.get_sms(page, per_page) client.get_sms_by_id(message_id) ``` ## Getting help For help installing or using the library, contact [support@instasent.com](mailto:support@instasent.com). Bugs and feature requests belong on the [GitHub repository](https://github.com/instasent). --- URL: https://docs.instasent.com/transactional-api/sdks/java # Java SDK Official Java client for the Instasent Transactional API. Download the JAR, construct an InstasentClient with your api_sms token, and call sendSms. Thin client over the Transactional HTTP endpoints. For anything outside the methods below, use a plain HTTP client — the [Reference](/transactional-api/http/reference) documents every endpoint. ## Installation Download the JAR and add it to your project's classpath: - [JAR](https://github.com/instasent/instasent-java-lib/releases/download/0.1.4/instasent-java-lib.jar) - [JAR with dependencies](https://github.com/instasent/instasent-java-lib/releases/download/v0.1.4/instasent-client-0.1.4-jar-with-dependencies.jar) ## Send an SMS ```java import com.instasent.InstasentClient; import java.io.IOException; import java.util.Map; public class Main { public static void main(String[] args) throws IOException { InstasentClient client = new InstasentClient(System.getenv("INSTASENT_TOKEN"), true); Map response = client.sendSms("My company", "+34666666666", "test message"); System.out.println(response); } } ``` ## Available methods ```java client.sendSms(sender, to, text, callback); client.getSms(page, perPage); client.getSmsById(messageId); ``` ## Getting help For help installing or using the library, contact [support@instasent.com](mailto:support@instasent.com). Bugs and feature requests belong on the [GitHub repository](https://github.com/instasent/instasent-java-lib).