# Instasent - transactional-api (full documentation) > Autocontained dump of every page under /a2p-messaging-api. Paste this into an AI assistant or feed it to an agent as context for transactional-api integration work. Some behaviour is cross-API: when a page here refers to another API or a shared concept, consult the zone index at https://docs.instasent.com/a2p-messaging-api/llms.txt (and, for product or dashboard questions, the whole-site index at https://docs.instasent.com/llms.txt). Source: https://docs.instasent.com/ Zone index: https://docs.instasent.com/a2p-messaging-api/llms.txt OpenAPI spec: https://docs.instasent.com/openapi/transactional.openapi.yaml --- URL: https://docs.instasent.com/a2p-messaging-api/overview # A2P Messaging API Send messages straight from your code — built for developers and AI agents. A simple REST API (and SMPP) with delivery tracking and webhooks: SMS today, with RCS and WhatsApp coming soon. No audience to manage: bring your recipients and send. **Language:** en **Audience:** developer **TLDR:** The A2P Messaging API sends messages from your code: SMS today, RCS and WhatsApp coming soon. One at a time (POST /sms) or in batches (POST /sms/bulk), with DLRs pushed to your webhook, two-way inbound, message history, traffic metrics, HLR lookups, balance and price profiles, over HTTP or SMPP. It is the Transactional API under its newer name: same endpoints, tokens and URLs. Use it when you manage your own recipient list; if Instasent should own the audience, use the platform. **Search keywords:** a2p messaging api, a2p, transactional api, sms api, smpp, bulk sms, dlr, hlr lookup **Docs index (every page):** https://docs.instasent.com/llms.txt **This zone's index:** https://docs.instasent.com/a2p-messaging-api/llms-full.txt **This page:** https://docs.instasent.com/a2p-messaging-api/overview/ (HTML) · https://docs.instasent.com/a2p-messaging-api/overview.md (Markdown) - [Send your first SMS](/a2p-messaging-api/http/quickstart) - [API Reference](/a2p-messaging-api/http/reference) #### 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 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" }), }); ``` #### python ```python import os, requests 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"}, ) ``` #### ✦ Your AI ```text Read https://docs.instasent.com/llms.txt and send a test SMS with the Instasent A2P Messaging API. My token is in $INSTASENT_TOKEN. ``` > **Note**: Also known as the **Transactional API** or **SMS API**: same endpoints, same tokens, same URLs, nothing to migrate. The A2P Messaging API is how your code sends messages through Instasent. It connects your application to carrier networks, delivering to handsets in 200+ countries, and covers everything around the send: single or batched sending, DLRs and inbound, message history, HLR lookups, price profiles and account balance. Today it sends **SMS**; **RCS and WhatsApp are coming soon**. 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)](/a2p-messaging-api/http/quickstart) - JSON over HTTPS. Best for application servers, serverless functions and anything that already speaks REST. Webhook-based DLRs. - [SMPP](/a2p-messaging-api/smpp/integration) - Persistent TCP sessions, binary protocol. Best for wholesale SMS and 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}`. - **Read traffic metrics** — `GET /sms/metrics` returns volume, delivery outcomes and cost per UTC day or month, by country, network and token. See [SMS traffic metrics](/a2p-messaging-api/http/metrics). - **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 A2P Messaging whenever your code needs to send — one message or many — 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`. - **Wholesale SMS over SMPP** — aggregators, messaging platforms and resellers pushing their own traffic through Instasent. [SMPP](/a2p-messaging-api/smpp/integration) gives carrier-grade throughput over persistent sessions; HTTP also works at moderate rates. - **Pre-send validation and pricing** — HLR lookup and price profiles to route and cost-check before dispatch. Coming from the [Legacy API](/a2p-messaging-api/legacy/overview)? This is its evolution: the same endpoint surface, modernised with DLR webhooks, per-project tokens, SMPP and the `api_sms` token model. New code goes here. And the dashboard uses the newer name, so what you create there is an *A2P Messaging API* token or project — the one a Transactional API integration already uses. If you need Instasent to own the audience, personalization, segmentation and attribution, use the [Product API](/platform-api/product-api/guide) instead — it delegates delivery to this one but adds the customer-data layer on top. The A2P Messaging API is the right pick when the caller already manages its recipient list and just needs to send. ## What to read next - [Channels](/a2p-messaging-api/channels/sms/senders) - SMS senders, destination countries and the wildcard route; RCS agents, coming soon. - [HTTP Quickstart](/a2p-messaging-api/http/quickstart) - Send your first SMS over REST in under five minutes. - [Bulk sending](/a2p-messaging-api/http/bulk) - Up to 100 messages per request, queue-based dispatch, the endpoint for API-driven campaigns. - [HTTP Authentication](/a2p-messaging-api/http/authentication) - Token types, header vs. query string, rotation. - [Receiving DLRs](/a2p-messaging-api/http/dlrs) - Webhook payload, status list, two-way inbound. - [SMS traffic metrics](/a2p-messaging-api/http/metrics) - Volume, delivery outcomes and cost per day or month, by country, network and token. - [Full API Reference](/a2p-messaging-api/http/reference) - Every endpoint, every parameter. --- This is one page of the Instasent documentation. For the complete machine-readable index of every guide and API reference, fetch https://docs.instasent.com/llms.txt — start there for full context. --- URL: https://docs.instasent.com/a2p-messaging-api/channels/sms/senders # SMS senders Every SMS sent through the A2P Messaging API goes out from a sender registered in your account. This page covers how a request identifies its sender, the 422 on an unregistered `from`, per-country registration with a legal profile, the exact spelling of a filed alias, and what is billed when a message is blocked. **Language:** en **Audience:** developer **TLDR:** Register your senders in the dashboard (SMS > Senders) and send from them: identify one by its id in `sender` (recommended) or by its name in `from`. A `from` that matches none of your senders returns 422 on `from`, and nothing is sent or charged. Where a country requires it, register the sender for that country with your legal profile, and send the alias exactly as filed, capitals included. Only wholesale providers who request the wildcard route can send an unregistered `from`. **Search keywords:** from field, sender id, sender name, unregistered sender, sender not found, 422 from, register sender, sender registration, legal profile, country registration, cnmc, alias spelling, exact alias, case sensitive sender, numeric sender, opt out substitution, unregisteredBypassRouteOptOut, blocked and charged, what is billed, fallback sender, wildcard sender **Related pages:** /a2p-messaging-api/channels/sms/wildcard-route, /a2p-messaging-api/channels/sms/destination-countries, /a2p-messaging-api/http/quickstart, /platform/en/channels/sms/coverage-and-delivery, /platform/en/channels/countries **Docs index (every page):** https://docs.instasent.com/llms.txt **This zone's index:** https://docs.instasent.com/a2p-messaging-api/llms-full.txt **This page:** https://docs.instasent.com/a2p-messaging-api/channels/sms/senders/ (HTML) · https://docs.instasent.com/a2p-messaging-api/channels/sms/senders.md (Markdown) The **sender** is the name or number the recipient sees as the origin of your SMS. In the A2P Messaging API it is not a free-text field: **every message goes out from a sender registered in your account**, the same senders you see in the dashboard under **SMS > Senders**. Registering them is what lets each message arrive under your brand, and in the countries that ask for it, what lets us file that brand with the regulator or the operators. The per-country rules — which countries accept any alphanumeric sender, which replace it with a number, which require a registration first — are the same for the dashboard and the API, and are documented once: - [Coverage & delivery](/platform/en/channels/sms/coverage-and-delivery) - What happens when you send to a country that requires registration and yours isn't approved yet. - [Countries](/platform/en/channels/countries) - Every destination, with what it asks for on each channel. ## Senders are required A request names its sender in one of two ways: - **`sender`** — the id of one of your senders. **This is the recommended form**: it is stable if the sender is renamed, and unambiguous. An id that doesn't exist returns `422`. - **`from`** — the sender's name: 3 to 11 characters when alphanumeric, 3 to 14 digits when numeric. It has to match one of your registered senders by name. When both are sent, `sender` wins, and the `from` in the response is the sender's canonical name. A `from` that matches none of your senders is **refused with `422`**, and **nothing is sent or charged**. The error is reported on the `from` field: ```json { "errors": { "fields": { "from": ["The sender is not registered in this project, register it before sending"] } } } ``` In a [bulk request](/a2p-messaging-api/http/bulk) the same check runs per item: a refused item is reported in `errors` and the rest are sent. > **Note**: The one exception is the [wildcard route](/a2p-messaging-api/channels/sms/wildcard-route), > for wholesale providers that carry third-party traffic under their own customers' sender > names. It is not available by default: it is requested from the SMS channel's **Settings** > tab in the dashboard. ## Register the sender in each country that requires it Creating the sender in your account is the first step. Countries with a regulator in force, such as Spain, also need an **accepted registration** of the sender for that country, which you request and follow in the **sender's sheet** with your company's legal profile. Which countries ask for it, how the sheet shows each one, and what happens while a registration is pending are in [SMS coverage](/a2p-messaging-api/channels/sms/coverage). ## Send the alias exactly as you filed it Where a sender is filed with a regulator, the check against that registry is **literal** — character by character, capitals included. Nothing is normalised on the way. If you filed **`PEDRO`** and your request carries **`Pedro`**, that check reads your alias as not filed, with all the consequences of not being filed — even though the procedure went through and was approved. It is worth a line in your integration, because it is the hardest failure to diagnose from the outside: the register shows as approved, the regulator has your alias, and the messages still don't arrive as they should. Sending by `sender` id avoids it; if you build `from` from a database field or a template, make sure it reproduces the filed spelling. ## Choosing blocking over substitution Some senders would rather not go out at all than go out with the recipient seeing a different sender — a brand where the name is the point, or traffic where an unexpected sender would raise a support case of its own. The sender carries a field for that: **`unregisteredBypassRouteOptOut`**. With it on, in a country whose regulator is in force, that sender never takes an alternative route — the block applies as it stands, and the message is reported as blocked by regulation. Two limits worth knowing before you set it: - **It changes nothing where there is no regulator**, and nothing before a regulator's rules take effect. It isn't a global "never substitute my sender" switch. - **It doesn't change what is billed** — see [What is billed](#what-is-billed). It lives on the sender, not on the account, on purpose: on an account with the wildcard route, the Wildcard Sender carries all of that account's unregistered traffic, so the choice belongs with the sender that carries it. ## What is billed **A message refused with `422` — for its sender or its [destination country](/a2p-messaging-api/channels/sms/destination-countries) — is not sent and not charged.** Once a message has been accepted, **a message blocked from a registered sender of your own is not billed**, while a blocked [transit message](/a2p-messaging-api/channels/sms/wildcard-route#what-is-billed) is. Delivery reports tell you the outcome per message — see [DLRs](/a2p-messaging-api/http/dlrs). --- This is one page of the Instasent documentation. For the complete machine-readable index of every guide and API reference, fetch https://docs.instasent.com/llms.txt — start there for full context. --- URL: https://docs.instasent.com/a2p-messaging-api/channels/sms/coverage # SMS coverage Which countries the A2P Messaging API can send SMS to, what each country asks of your sender, how that shows in the sender's sheet in the dashboard, and how coverage differs from the destination countries you allow. **Language:** en **Audience:** developer **TLDR:** Coverage is where Instasent can deliver your SMS and on what conditions; destination countries are where you choose to allow sending. Every country and what it asks for is in the countries catalog, also published as JSON. In most countries creating the sender is enough; countries with a regulator in force, such as Spain, need an accepted registration for that country, and without it delivery is best-effort and billed. The sender's sheet shows each country under Needing your attention, In review or Approved countries. **Search keywords:** coverage, sms coverage, supported countries, which countries, country requirements, country registration, regulator, cnmc, registration required, approved countries, in review, needing your attention, country-requirements.json, best effort, price per country **Related pages:** /platform/en/channels/countries, /platform/en/channels/sms/coverage-and-delivery, /a2p-messaging-api/channels/sms/senders, /a2p-messaging-api/channels/sms/destination-countries **Docs index (every page):** https://docs.instasent.com/llms.txt **This zone's index:** https://docs.instasent.com/a2p-messaging-api/llms-full.txt **This page:** https://docs.instasent.com/a2p-messaging-api/channels/sms/coverage/ (HTML) · https://docs.instasent.com/a2p-messaging-api/channels/sms/coverage.md (Markdown) **Coverage** is where Instasent can deliver your SMS and on what conditions: which countries you can reach, and what each of them asks of your sender before it accepts it as yours. It is decided by each country's regulator and operators, not by you — and it is a different thing from your [destination countries](/a2p-messaging-api/channels/sms/destination-countries), which are where *you* choose to allow sending. ## Where SMS can be sent The countries and what each one asks for are documented once, in the countries catalog, for the dashboard and the API alike: - [Countries](/platform/en/channels/countries) - Every destination, with what it asks for on SMS and RCS. - [Coverage & delivery](/platform/en/channels/sms/coverage-and-delivery) - What best-effort delivery means while a registration is pending, and how it is billed. The same catalog is published as JSON, so your integration can read it instead of copying it: [`country-requirements.json`](https://docs.instasent.com/data/country-requirements.json), with its [JSON Schema](https://docs.instasent.com/data/country-requirements.schema.json). Each country states whether a channel is available there and whether we have documented its requirements; a country marked as not reviewed means we haven't documented it yet — not that it asks for nothing. What each country costs for your account is a separate question, answered by `GET /sms/price-profile/me/countries` (see the [API Reference](/a2p-messaging-api/http/reference)). ## What each country asks of your sender There are two cases: - **In most countries, creating the sender is enough.** Once it is [registered in your account](/a2p-messaging-api/channels/sms/senders), you can send to them. - **Countries with a regulator in force, such as Spain, need an accepted registration** of your sender for that country. You request it with your company's **legal profile**, which the regulator and the operators check before approving, and they may ask for more documents. While a required registration isn't accepted, the message is still attempted: delivery is **best-effort**, the recipient may see a different sender, and it is billed — see [Coverage & delivery](/platform/en/channels/sms/coverage-and-delivery). The country's page in the catalog says which case applies, and what to prepare. Your reports, exports, DLRs and webhooks always show the sender **you sent**. Where the sender is substituted, what changes is what reaches the handset — never what your integration reads back. ## How it shows in the dashboard Each sender's registrations are in its **sheet**, in the SMS channel's **Senders** tab. That is where you request a country and follow it, grouped in three sections: - **Needing your attention** — countries where something is waiting on you. - **In review** — registrations submitted and not yet decided. A country in review **doesn't count as approved**. - **Approved countries** — where the sender is registered. ## Coverage and your destination countries The two settings answer different questions, and a message needs both: - **Coverage** — can Instasent deliver there, and under which conditions for your sender? It doesn't refuse your request: a message to a country whose registration is pending is attempted best-effort, and its outcome arrives in the [DLRs](/a2p-messaging-api/http/dlrs). - **[Destination countries](/a2p-messaging-api/channels/sms/destination-countries)** — have you allowed sending there? A country outside your lists is refused with `422` on `to`, and nothing is sent or charged. Wholesale providers on the [wildcard route](/a2p-messaging-api/channels/sms/wildcard-route) have one more layer: the transit registration per country, explained on that page. --- This is one page of the Instasent documentation. For the complete machine-readable index of every guide and API reference, fetch https://docs.instasent.com/llms.txt — start there for full context. --- URL: https://docs.instasent.com/a2p-messaging-api/channels/sms/destination-countries # Destination countries & SMS pumping Destination countries limit where your A2P Messaging API project can send SMS, for every sender, over HTTP and SMPP. This page explains SMS pumping fraud, how the allowed and blocked country lists defend against it, and what a request to a country that isn't allowed returns. **Language:** en **Audience:** developer **TLDR:** SMS pumping is bots abusing forms that send an SMS (sign-up, OTP, password reset) to trigger messages to numbers where the attacker earns part of the fee; you pay for those sends. In SMS > Settings > Destination countries, Allowed countries limits sending to the countries you list (empty means every country) and Blocked countries excludes countries on top. Both apply to every sender of the project, over HTTP and SMPP. A send to a country that isn't allowed returns 422 on `to`, and nothing is sent or charged. **Search keywords:** sms pumping, AIT, artificially inflated traffic, toll fraud, IRSF, geo permissions, block countries, destination countries, allowed countries, blocked countries, country allowlist, country blocklist, otp fraud, 422 to, country not allowed **Related pages:** /a2p-messaging-api/channels/sms/senders, /a2p-messaging-api/http/errors, /a2p-messaging-api/http/bulk **Docs index (every page):** https://docs.instasent.com/llms.txt **This zone's index:** https://docs.instasent.com/a2p-messaging-api/llms-full.txt **This page:** https://docs.instasent.com/a2p-messaging-api/channels/sms/destination-countries/ (HTML) · https://docs.instasent.com/a2p-messaging-api/channels/sms/destination-countries.md (Markdown) **Destination countries** decide where your project can send SMS. They are the defence against **SMS pumping**, one of the most common frauds on any service that sends SMS from a public form, and they cost nothing to set: if your business works in a few countries, listing them closes every other destination to anyone abusing your integration. ## What SMS pumping is SMS pumping — also called **artificially inflated traffic (AIT)** — is fraud that exploits forms that send an SMS: sign-up, one-time passcodes (OTP), password reset. Bots fill those forms in with numbers in countries where the attacker, in league with someone along the delivery chain, **earns a share of the fee for every message delivered**. No real user is behind those requests. The messages are sent and delivered normally, so **you pay for them**. The usual signs are a sudden rise in sends to countries where you have no customers, often to consecutive numbers, with no one completing the flow the SMS was for. ## Limiting the countries you send to In the dashboard, the A2P Messaging API project's SMS channel has a **Settings** tab with a **Destination countries** block — its ⓘ explains SMS pumping — and two lists: - **Allowed countries** — if your customers are in only a few countries, list them here, and nothing goes anywhere else. **Empty means every country.** - **Blocked countries** — specific countries your project never sends to. They apply **on top of** the allowed list: a country is blocked even if it is also in the allowed list. **A new account can send to every country**: both lists start empty. Whether to limit any country is your decision. Both lists apply to **every sender of the project**, and to traffic sent over [HTTP](/a2p-messaging-api/http/quickstart) and [SMPP](/a2p-messaging-api/smpp/integration) alike — transit traffic on the wildcard route included. Nothing in this block is saved until you select **Save**. ## What a request to a country that isn't allowed returns A message to a country outside your allowed list, or in your blocked list, is **refused when you send it, with `422`**, and **nothing is sent or charged**. The error is reported on the `to` field: ```json { "errors": { "fields": { "to": ["The destination country is not allowed by the project settings"] } } } ``` In a [bulk request](/a2p-messaging-api/http/bulk) the check runs per item: the refused message is reported in `errors`, and the rest of the batch is sent. ## Good practice - **Use Allowed countries if your business is in one or a few countries.** It is the single setting that does most against pumping, because it closes every destination you didn't choose instead of chasing the ones attackers use today. - **Protect your own forms.** The SMS is triggered from your sign-up, OTP or password-reset forms, so the first line of defence is there: rate-limit how many codes a phone number, an IP address or a session can request, and put a CAPTCHA in front of the request. --- This is one page of the Instasent documentation. For the complete machine-readable index of every guide and API reference, fetch https://docs.instasent.com/llms.txt — start there for full context. --- URL: https://docs.instasent.com/a2p-messaging-api/channels/sms/wildcard-route # Wildcard route The wildcard route lets wholesale providers send SMS whose sender is not registered in their account, under the Wildcard Sender (in transit). This page covers who it is for, how to request it from the dashboard, what happens to that traffic in each country, the CNMC provider requirement in Spain, SMPP, and what is billed. **Language:** en **Audience:** developer **TLDR:** Not available by default: registering senders is required, and an unregistered `from` returns 422. Wholesale providers carrying third-party transit traffic request the wildcard route in the dashboard (SMS > Settings) with their wholesale legal profile, and Instasent enables it. Then a `from` matching none of their senders travels under the Wildcard Sender (in transit), best-effort where registration is required; Spain also needs the company registered with the CNMC as a provider. SMPP keys need it. Blocked transit messages are billed. **Search keywords:** wildcard route, wildcard sender, fallback sender, in transit, transit traffic, transit registration, wholesale, wholesale provider, reseller, aggregator, request wildcard route, wholesale legal profile, unregistered sender, cnmc provider registry, numeric sender, smpp, blocked and charged **Related pages:** /a2p-messaging-api/channels/sms/senders, /a2p-messaging-api/channels/sms/destination-countries, /a2p-messaging-api/smpp/authentication, /platform/en/channels/sms/coverage-and-delivery **Docs index (every page):** https://docs.instasent.com/llms.txt **This zone's index:** https://docs.instasent.com/a2p-messaging-api/llms-full.txt **This page:** https://docs.instasent.com/a2p-messaging-api/channels/sms/wildcard-route/ (HTML) · https://docs.instasent.com/a2p-messaging-api/channels/sms/wildcard-route.md (Markdown) The **wildcard route** is an exception to the rule that every message goes out from a [sender registered in your account](/a2p-messaging-api/channels/sms/senders). It exists for one kind of account: **wholesale providers** that carry the traffic of third parties under their own customers' sender names — transit traffic, where registering every sender in advance isn't possible. With the route enabled, a `from` that matches none of your senders is no longer refused with `422`: it travels under your **Wildcard Sender** — in full, the **Wildcard Sender (in transit)**: *wildcard* is how the dashboard names it, and *in transit* is how regulators refer to the traffic it carries. It is a sender with no alias of its own: the alias the recipient should see travels in each message, in your `from`. **Without the route, nothing changes for you**: register your senders and send from them. If your traffic is your own brand's, that is the setup to use. Accounts that were already sending through the wildcard route keep it. ## Where the route shows in the dashboard In the A2P Messaging API project, the SMS channel has a **Settings** tab, after **Overview**, **Senders** and **Messages**. Its **Wildcard route** block shows the route's status: - **Enabled** — *"Messages whose sender isn't registered in this project go out under your Wildcard Sender."*, with a **Manage registrations** button. - **Not enabled** — *"Every message needs a sender registered in this project. The wildcard route, for wholesale providers, is available on request."*, with a **Request the wildcard route** button. The same status is on every other tab of the SMS channel, in the **Wholesale providers** section of the right-hand panel: a **Wildcard route** row with **Manage** or **Request**, next to the [SMPP](/a2p-messaging-api/smpp/authentication#enabling-smpp-on-your-account) row. Without the route, the senders table has no **Wildcard route** row. With it, the Wildcard Sender is listed there — in its sheet, the **Wildcard Sender (in transit)**. ## Requesting the wildcard route The route is not available by default, and you never turn it on yourself: you request it from the dashboard. #### 1. Open the SMS settings In the A2P Messaging API project, go to the SMS channel's **Settings** tab and select **Request the wildcard route** (or **Request** in the **Wholesale providers** section). #### 2. Choose your wholesale legal profile Under **Choose your wholesale legal profile**, pick your company's legal profile, or create one. It is the profile the regulators will see as the provider carrying the traffic. #### 3. Send the request Confirm with **Send request**. Once the route is granted, you can't turn it off from the dashboard. Today the route is granted at once, and the dashboard confirms it with *"Request sent. Wildcard route enabled"*. Requesting it needs permission to edit the project and the owner, admin or manager role in the organization. ## What happens to transit traffic Each regulated country decides how transit traffic is treated, and two things decide whether a message gets through: - **A transit registration for that country.** In countries with an active regulator, such as Spain, the Wildcard Sender needs a registration for that country. From **Manage registrations** in the SMS **Settings** tab, tell us which countries to cover in transit; our team reviews each request against the regulator before activating it. - **Your company registered with that regulator as a provider**, where the regulator requires it — see [Spain](#spain-your-company-has-to-be-a-registered-provider) below. Without them, **delivery is best-effort**, along the lines set out in [Coverage & delivery](/platform/en/channels/sms/coverage-and-delivery): we maximise the chances of the message arriving, without promising it will, and the recipient may see a different sender. In some countries there is no alternative at all, and a message the operator won't accept doesn't go out. Countries that need no registration take transit traffic as is. Only a `from` can produce transit traffic: a message identified by `sender` id always uses that sender. And your [destination countries](/a2p-messaging-api/channels/sms/destination-countries) apply to transit traffic like to any other. ## Spain: your company has to be a registered provider This is the requirement most likely to catch an integration out, because it is met **outside Instasent** and nothing in your code will tell you it is missing. To carry traffic into Spain under the Wildcard Sender, your company has to be a **provider (PRO)** and be **registered as one with the CNMC**. We supply the identifier you filed with them — taken from your legal profile, or from your organisation's details — and the CNMC's own registry is what decides: if your company isn't listed and active there, the message doesn't go out. **The authority is their registry, not any status in our system.** A registration can look fine on our side and still be refused because the provider check fails. > **Warning**: If you route Spanish traffic under the Wildcard Sender and your company isn't registered > with the CNMC as a provider, sort that out before the traffic matters. It is a filing with > the regulator, not a setting in the dashboard. ## A numeric `from` doesn't qualify The alias the regulators examine for transit traffic is the one in your `from`. **A `from` made of digits never takes an alternative route**: where an alphanumeric `from` would be carried best-effort, a numeric one is not. That splits into two very different cases, and the difference is whether what you sent is a real phone number: - **A valid international phone number** doesn't need an alternative route in Spain: it is **exempt from the CNMC** and accepted without any filing. - **A string of digits that isn't a phone number** — `123`, an internal code, a short reference — is neither exempt nor carried: it has no route and no exemption, so it doesn't go out. If you send numeric senders, send real phone numbers. ## SMPP needs the wildcard route SMPP traffic always carries your customer's own sender name in `source_addr`, so it relies on this route: **SMPP keys can only be created when the account has both SMPP access and the wildcard route**. How to request SMPP access is in [SMPP authentication](/a2p-messaging-api/smpp/authentication#enabling-smpp-on-your-account). ## What is billed **A blocked transit message is billed.** A blocked message sent from a registered sender of your own is not. That asymmetry is deliberate, and it is the most concrete reason to register the senders and countries you can, even with the route enabled: traffic riding the Wildcard Sender costs the same whether it arrives or not. A message refused with `422` — for its [destination country](/a2p-messaging-api/channels/sms/destination-countries), for instance — is not sent and not charged. ## RCS doesn't work this way There is no wildcard route on RCS, and there won't be. An RCS agent is always tied to a known provider, so there is no sender without an alias for traffic to travel under: the agent has to be registered for each country before anything is sent there. See [RCS agents](/a2p-messaging-api/channels/rcs/agents). --- This is one page of the Instasent documentation. For the complete machine-readable index of every guide and API reference, fetch https://docs.instasent.com/llms.txt — start there for full context. --- URL: https://docs.instasent.com/a2p-messaging-api/channels/rcs/agents # RCS agents Sending RCS through the A2P Messaging API is coming soon. This page explains what you can do today: register your RCS agent in the A2P Messaging API project, country by country, so it is approved by the time sending is available. **Language:** en **Audience:** developer **TLDR:** Sending RCS through the A2P Messaging API is coming soon: there is no RCS endpoint yet. You can already register your RCS agent from the dashboard (Channels > RCS > Senders in the A2P Messaging API project), so it is approved when sending arrives. Each agent needs a registration per country, and a country without one is blocked: RCS has no wildcard route. An agent is tied to one provider. The registration process is the same as on the platform. **Search keywords:** rcs, rcs api, send rcs, rcs agent, rcs sender, register rcs agent, rcs registration, rcs countries, rich communication services, branded messaging, verified sender, coming soon **Related pages:** /platform/en/channels/rcs/senders-verification, /platform/en/channels/rcs/availability, /a2p-messaging-api/channels/sms/wildcard-route **Docs index (every page):** https://docs.instasent.com/llms.txt **This zone's index:** https://docs.instasent.com/a2p-messaging-api/llms-full.txt **This page:** https://docs.instasent.com/a2p-messaging-api/channels/rcs/agents/ (HTML) · https://docs.instasent.com/a2p-messaging-api/channels/rcs/agents.md (Markdown) **RCS** is messaging that arrives in the phone's default messaging app with your verified brand up front — name, logo and colour — instead of a bare sender name. On RCS the sender is an **agent**: the brand identity you register, and that the operators approve, before anything is sent. > **Note**: **Sending RCS through the A2P Messaging API is coming soon.** There is no RCS endpoint yet, > and the request format is not published. Today the API sends SMS. ## Register your agent now Approving an RCS agent takes time — operators and the network review it country by country — so you don't have to wait for the endpoint to start. You can already **create your agent and register it** in your A2P Messaging API project, and have it approved by the time sending through the API is available. In the dashboard, the A2P Messaging API project has the RCS channel under **Channels > RCS**, and the agents are in its **Senders** tab. The RCS channel has no **Settings** tab: everything about the agent is done from the agent itself. The registration is the **same process as on the platform**: create the sender, register it for each country, complete the organization and compliance data, and follow each register's status until it is active. The one difference: an A2P Messaging API project has **no default RCS sender**, because each API call names its sender. The process is documented once: - [RCS senders & verification](/platform/en/channels/rcs/senders-verification) - The full registration process, step by step, with every status. - [Your RCS agent profile](/platform/en/channels/rcs/agent-profile) - Every field your contacts will see, with the live previews. - [RCS agent best practices](/platform/en/channels/rcs/agent-best-practices) - How to design an agent that gets approved the first time. - [RCS acceptable use](/platform/en/channels/rcs/acceptable-use) - What an RCS agent can and can't send. ## Coverage As on [SMS](/a2p-messaging-api/channels/sms/coverage), **coverage** is where Instasent can deliver and on what conditions, and the countries and what each one asks for are documented once, in the countries catalog: - [RCS countries & availability](/platform/en/channels/rcs/availability) - The countries where RCS is available. - [Countries](/platform/en/channels/countries) - Every destination, with what it asks for on SMS and RCS. The same catalog is published as JSON: [`country-requirements.json`](https://docs.instasent.com/data/country-requirements.json), where each country states whether RCS is available there. The countries you can register your agent in are the ones the dashboard offers when you register it. What each country asks of your agent differs from SMS in one way: **on RCS there is always a registration**. The agent is registered **per country**, each registration is approved separately, and a country where your agent has no approved registration is a country you can't send RCS to — not registering is what blocks it. There is no best-effort delivery for an unregistered agent, and no list of destination countries to set: the agent's registrations *are* where it can send. The agent's registrations, and the status of each, are followed from the agent in the RCS channel's **Senders** tab — see [RCS senders & verification](/platform/en/channels/rcs/senders-verification). ## No wildcard route on RCS RCS has no equivalent of the SMS [wildcard route](/a2p-messaging-api/channels/sms/wildcard-route), and there won't be one. An RCS agent is always **tied to one provider**, so there is no sender without an identity for traffic to travel under: the agent has to be registered for each country before anything is sent there. --- This is one page of the Instasent documentation. For the complete machine-readable index of every guide and API reference, fetch https://docs.instasent.com/llms.txt — start there for full context. --- URL: https://docs.instasent.com/a2p-messaging-api/http/quickstart # A2P Messaging API quickstart Send your first SMS over the A2P Messaging API in under five minutes. Grab a token, fire one HTTP request, and point a webhook at the DLR URL to see delivery. **Language:** en **Audience:** developer **TLDR:** Send an SMS with POST https://api.instasent.com/transactional/v1/sms, header Authorization: Bearer , and a JSON body containing from, to, and text. The response returns an id; point your DLR webhook at it to track delivery. **Docs index (every page):** https://docs.instasent.com/llms.txt **This zone's index:** https://docs.instasent.com/a2p-messaging-api/llms-full.txt **This page:** https://docs.instasent.com/a2p-messaging-api/http/quickstart/ (HTML) · https://docs.instasent.com/a2p-messaging-api/http/quickstart.md (Markdown) The A2P Messaging 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](/a2p-messaging-api/http/bulk) — same token, one call, up to 100 messages per request. #### 1. Create an Instasent account Sign up at [dashboard.instasent.com/a2p](https://dashboard.instasent.com/a2p) and complete onboarding: **Messaging API** comes preselected as what you will use Instasent for. #### 2. Create a token Your A2P Messaging API project already comes with an API key named **Onboarding** for the first send: copy it from the project home. To create more, select **API tokens** in the project's sidebar and click **Create API token**. Treat the token 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](/a2p-messaging-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](/a2p-messaging-api/http/bulk). > **Note**: **About that `from`.** It has to be a sender registered in your account (SMS > > Senders): one that matches none is refused with `422` on `from`, and nothing is sent or > charged. Only wholesale providers who have requested the wildcard route can send an > unregistered `from`. Some countries also require the sender to be registered with the > regulator, and your account may restrict its destination countries (`422` on `to`). > Before you route real traffic, read > [SMS senders](/a2p-messaging-api/channels/sms/senders). ## What's next - **[SMS senders](/a2p-messaging-api/channels/sms/senders)** — why the `from` must be a registered sender, per-country registration, and what is billed when a message is blocked. - **[Destination countries & SMS pumping](/a2p-messaging-api/channels/sms/destination-countries)** — the allowed and blocked country lists, and the `422` on `to`. - **[Wildcard route](/a2p-messaging-api/channels/sms/wildcard-route)** — for wholesale providers carrying transit traffic: how to request it. - **[Bulk sending](/a2p-messaging-api/http/bulk)** — `POST /sms/bulk`, 100 messages per request, queue-based throughput. The endpoint to use for campaigns. - **[Authentication](/a2p-messaging-api/http/authentication)** — token scopes, rotation, header vs. query-string. - **[Rate limits](/a2p-messaging-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](/a2p-messaging-api/http/errors)** — status codes, error payload shape, retry guidance. - **[Receiving DLRs](/a2p-messaging-api/http/dlrs)** — webhook payload and status list. - **[SMS traffic metrics](/a2p-messaging-api/http/metrics)** — `GET /sms/metrics`: volume, delivery outcomes and cost per UTC day or month, by country, network and token. - **[Reference](/a2p-messaging-api/http/reference)** — every endpoint, every parameter. --- This is one page of the Instasent documentation. For the complete machine-readable index of every guide and API reference, fetch https://docs.instasent.com/llms.txt — start there for full context. --- URL: https://docs.instasent.com/a2p-messaging-api/http/bulk # Bulk sending Send many SMS in a single request via POST /sms/bulk — the A2P Messaging API's throughput endpoint for campaigns, notification fan-outs and high-volume dispatch. Covers the request and response shape, per-request limits, the queue-based throughput model, partial-success handling and when to prefer SMPP. **Language:** en **Audience:** developer **TLDR:** POST /sms/bulk takes a top-level JSON array of 1 to 100 messages with the same fields as POST /sms, and returns 201 with entity (the accepted messages, each with its id) and errors (rejected items, with per-field messages): a 201 does not mean every item was accepted. Accepted messages are queued and dispatched at carrier speed, each with its own DLRs; one bulk call counts as one request against the rate limit, and more than 100 items returns 413. **Docs index (every page):** https://docs.instasent.com/llms.txt **This zone's index:** https://docs.instasent.com/a2p-messaging-api/llms-full.txt **This page:** https://docs.instasent.com/a2p-messaging-api/http/bulk/ (HTML) · https://docs.instasent.com/a2p-messaging-api/http/bulk.md (Markdown) The maximum throughput you can get out of the A2P Messaging 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 {entity, 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 accepted messages under `entity` (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 { entity, 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 { "entity": [ { "id": "...", "clientId": null, "from": "Instasent", "to": "+34600000001", "text": "Hello one", "status": "...", "..." : "..." } ], "errors": [ { "fields": { "to": ["This value is not valid"] } } ] } ``` - `entity`: one object per **accepted** message, with its own Instasent `id`. The key is singular but always holds an array. 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`. 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](/a2p-messaging-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](/a2p-messaging-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](/a2p-messaging-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 entity and errors Iterate both arrays. For each item in `entity`, 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](/a2p-messaging-api/http/dlrs)** — webhook payload, status list, per-message matching. - **[SMS traffic metrics](/a2p-messaging-api/http/metrics)** — your traffic in aggregate, bulk included: volume, delivery outcomes and cost per UTC day or month, by country, network and token (not per batch). - **[Rate limits](/a2p-messaging-api/http/rate-limits)** — how the window is counted and how to ask for more. - **[Errors](/a2p-messaging-api/http/errors)** — status codes and retry guidance, including `413` and `422`. - **[SMPP integration](/a2p-messaging-api/smpp/integration)** — the next step up when HTTP is not enough. - **[API Reference](/a2p-messaging-api/http/reference)** — full shape of `POST /sms/bulk` and every other endpoint. --- This is one page of the Instasent documentation. For the complete machine-readable index of every guide and API reference, fetch https://docs.instasent.com/llms.txt — start there for full context. --- URL: https://docs.instasent.com/a2p-messaging-api/http/authentication # A2P Messaging API authentication The A2P Messaging 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. **Language:** en **Audience:** developer **TLDR:** Create an api_sms token for the project in the dashboard under API tokens (it is shown once) and send it as Authorization: Bearer ; the access_token query-string parameter also works, but only for quick tests from trusted shells. Tokens don't expire: to rotate one, create the replacement first, redeploy, then delete the old token, and any request still using it gets 401 Unauthorized. **Search keywords:** api key, api token **Docs index (every page):** https://docs.instasent.com/llms.txt **This zone's index:** https://docs.instasent.com/a2p-messaging-api/llms-full.txt **This page:** https://docs.instasent.com/a2p-messaging-api/http/authentication/ (HTML) · https://docs.instasent.com/a2p-messaging-api/http/authentication.md (Markdown) Every request to the A2P Messaging 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](/a2p-messaging-api/http/rate-limits)** — how many requests per minute and what the `X-RateLimit-*` headers tell you. - **[Errors](/a2p-messaging-api/http/errors)** — status codes returned by the API and how to retry safely. --- This is one page of the Instasent documentation. For the complete machine-readable index of every guide and API reference, fetch https://docs.instasent.com/llms.txt — start there for full context. --- URL: https://docs.instasent.com/a2p-messaging-api/http/rate-limits # A2P Messaging API rate limits A2P Messaging HTTP traffic is rate-limited per account and per endpoint. Each response reports the current window through X-RateLimit headers, and breaching the limit returns 429. **Language:** en **Audience:** developer **TLDR:** Limits are counted per account and per endpoint over a 60-second window, shared by all the account's tokens: 1200 requests for POST /sms, POST /sms/bulk (one call is one request, with its own counter) and most endpoints, but only 10 for GET /sms and GET /sms/metrics and 20 for GET /sms/{id}, so track delivery with DLR webhooks instead of polling. Going over returns 429 with a plain-text body; retry after X-RateLimit-Reset. For a higher ceiling, open a ticket. **Docs index (every page):** https://docs.instasent.com/llms.txt **This zone's index:** https://docs.instasent.com/a2p-messaging-api/llms-full.txt **This page:** https://docs.instasent.com/a2p-messaging-api/http/rate-limits/ (HTML) · https://docs.instasent.com/a2p-messaging-api/http/rate-limits.md (Markdown) Every A2P Messaging endpoint enforces a ceiling of requests per 60-second window. The counter is kept per account and per endpoint: all the tokens of an account share one counter for each endpoint, so spreading traffic across several tokens does not add capacity. 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. ## Limits per endpoint | Endpoint | Requests per 60 seconds | | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | | `POST /sms` | 1200 | | `POST /sms/bulk` | 1200. One call counts as one request, however many messages it carries, and it has its own counter, separate from `POST /sms`. | | `GET /sms` | 10 | | `GET /sms/{id}` | 20 | | `GET /sms/metrics` | 10 | | `POST /lookup`, `GET /lookup`, `GET /lookup/{id}` | 1200 each | | `GET /organization/account` | 1200 | | `GET /sms/price-profile/me/countries`, `GET /lookup/price-profile/me/countries` | 1200 each | With so few reads allowed on `GET /sms` and `GET /sms/{id}`, track delivery through [DLR webhooks](/a2p-messaging-api/http/dlrs) instead of polling. For counts over a day or a month, read [SMS traffic metrics](/a2p-messaging-api/http/metrics) instead of paging through `GET /sms`; its figures refresh periodically, so calling it more often than they change gains nothing. ## 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 the plain text `You exceeded the rate limit` (not JSON); 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](/a2p-messaging-api/http/errors)** — every status code you might receive, including `429`. - **[API Reference](/a2p-messaging-api/http/reference)** — per-endpoint documentation. --- This is one page of the Instasent documentation. For the complete machine-readable index of every guide and API reference, fetch https://docs.instasent.com/llms.txt — start there for full context. --- URL: https://docs.instasent.com/a2p-messaging-api/http/errors # A2P Messaging API errors The A2P Messaging HTTP API uses standard HTTP status codes. This page lists the ones you will encounter, what they mean and whether a retry is safe. **Language:** en **Audience:** developer **TLDR:** Branch on the HTTP status, not on the message text: most error bodies are JSON with error.message and error.code, but the 429 body is plain text. 400, 401, 402 (out of balance), 403, 404, 413 and 422 (a field fails validation: an unregistered sender on `from`, a destination country that is not allowed on `to`) mean the request or the account needs fixing and retrying won't help; retry only 429 and 5xx, with exponential backoff starting at 1 second. **Docs index (every page):** https://docs.instasent.com/llms.txt **This zone's index:** https://docs.instasent.com/a2p-messaging-api/llms-full.txt **This page:** https://docs.instasent.com/a2p-messaging-api/http/errors/ (HTML) · https://docs.instasent.com/a2p-messaging-api/http/errors.md (Markdown) 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. Most error bodies are JSON with an `error` object that carries a human-readable `message` and the numeric `code`; the exception is `429`, whose body is plain text. Branch your code on the HTTP status, not on the `message` text, which is written for people and can change. ## 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 ``` When the body is not valid JSON: ```json { "error": { "message": "Invalid JSON", "code": 400 } } ``` When the body of `POST /sms/bulk` is not a JSON array of objects (for example, a single object): ```json { "error": { "message": "Body should be a JSON array of objects", "code": 400 } } ``` `GET /sms/metrics` also returns `400` when its query parameters are missing or malformed, or ask for a wider window than allowed. See [SMS traffic metrics](/a2p-messaging-api/http/metrics#errors-and-limits). ### `401 Unauthorized` The token is missing or does not exist (mistyped, rotated or deleted), or the request comes from an IP address outside the token's accepted IPs. Not retryable as is: check the token and the network you call from, and see the [Authentication](/a2p-messaging-api/http/authentication) guide. ```http HTTP/1.1 401 Unauthorized ``` ```json { "error": { "message": "No token could be found.", "code": 401 } } ``` ### `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 ``` ### `403 Forbidden` The token exists but cannot be used for this request: it is disabled, the user or account is not enabled or is pending verification, or it is not an A2P Messaging API token (`api_sms`) with HTTP access. `GET /sms/{id}` also returns `403` when the message belongs to another account. Not retryable until the cause is fixed. The `message` differs from one cause to another, so branch on the `403` status rather than parsing the text. ```http HTTP/1.1 403 Forbidden ``` ### `404 Not Found` The resource (message id, token id, project) does not exist or is not visible to the current token. `GET /sms/metrics` returns it when the account has no A2P Messaging API project. ```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 ``` ### `422 Unprocessable Entity` The request is well formed but a field fails validation. The body is a JSON object with the messages per field under `errors.fields` — for instance `errors.fields.from` or `errors.fields.to`, each an array of strings. Nothing is sent and nothing is charged. Not retryable as is: fix the field. The cases you are most likely to meet: - **Sender not registered** — error on `from`: *"The sender is not registered in this project, register it before sending"*. Every message needs a [sender registered in your account](/a2p-messaging-api/channels/sms/senders), unless the account has the [wildcard route](/a2p-messaging-api/channels/sms/wildcard-route) for wholesale providers. A `sender` id that doesn't exist also returns `422`. - **Destination not allowed** — error on `to`: *"The destination country is not allowed by the project settings"*. The country is outside your project's allowed list or in its blocked list; see [Destination countries](/a2p-messaging-api/channels/sms/destination-countries). - **Field validation** — a missing or malformed field, such as a `to` that isn't a valid number or a `from` outside its length limits. The field named in `errors.fields` is the one to fix. On `POST /sms/bulk` these are reported per item in `errors`, and the rest of the batch is sent — except a `sender` id that doesn't exist, which refuses the whole call with `422`. See [Bulk sending](/a2p-messaging-api/http/bulk). ```http HTTP/1.1 422 Unprocessable Entity ``` ### `429 Too Many Requests` You hit the per-endpoint rate limit. The body is the plain text `You exceeded the rate limit`, not JSON, so do not try to parse it. The `X-RateLimit-Reset` header says when to retry. See [Rate limits](/a2p-messaging-api/http/rate-limits). ```http HTTP/1.1 429 Too Many Requests You exceeded the rate limit ``` ### `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](/a2p-messaging-api/http/dlrs)** — delivery reporting for messages that did leave the API successfully. - **[API Reference](/a2p-messaging-api/http/reference)** — per-endpoint responses. --- This is one page of the Instasent documentation. For the complete machine-readable index of every guide and API reference, fetch https://docs.instasent.com/llms.txt — start there for full context. --- URL: https://docs.instasent.com/a2p-messaging-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. **Language:** en **Audience:** developer **TLDR:** Delivery reports are POSTed as JSON (id, clientId, status, code, eventAt) to the webhook configured per api_sms token; with two-way enabled, inbound messages arrive on the same endpoint with a message field. Reply 2xx in under 5 seconds, since anything else is retried with exponential backoff. One message can produce several DLRs (sent, buffered, delivered…), so key on id and keep the latest status. **Docs index (every page):** https://docs.instasent.com/llms.txt **This zone's index:** https://docs.instasent.com/a2p-messaging-api/llms-full.txt **This page:** https://docs.instasent.com/a2p-messaging-api/http/dlrs/ (HTML) · https://docs.instasent.com/a2p-messaging-api/http/dlrs.md (Markdown) 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. If what you need from DLRs is counts rather than per-message state (how many were delivered or failed per day, country, network or token), [SMS traffic metrics](/a2p-messaging-api/http/metrics) returns them already aggregated. ## What's next - **[Errors](/a2p-messaging-api/http/errors)** — for failures that happen before the message even leaves the API. - **[SMS traffic metrics](/a2p-messaging-api/http/metrics)** — the same outcomes counted for you per UTC day or month (delivered, failed, messages without a report…), by country, network and token, without storing every DLR. - **[API Reference](/a2p-messaging-api/http/reference)** — retrieve the current status of a message on demand. --- This is one page of the Instasent documentation. For the complete machine-readable index of every guide and API reference, fetch https://docs.instasent.com/llms.txt — start there for full context. --- URL: https://docs.instasent.com/a2p-messaging-api/http/metrics # SMS traffic metrics Read the volume, delivery outcomes and cost of the SMS sent with your API tokens, per UTC day or month, with a breakdown by country, network and token, in a single call to GET /sms/metrics. **Language:** en **Audience:** developer **TLDR:** GET /transactional/v1/sms/metrics?from=YYYY-MM-DD&to=YYYY-MM-DD returns totals, a zero-filled series per UTC day (up to 92 days) or month (granularity=month, up to 1100 days) and a breakdown by country, network and token, for all your API tokens or one (tokenId). Figures are aggregated, not real time: only charged messages, by charge date, test traffic excluded, cost in EUR. dataUpTo says how fresh they are; buckets from finalBefore on may still change. 10 requests per minute. **Search keywords:** metrics, statistics, stats, usage, analytics, report, reporting, delivery rate, delivery stats, cost, spend, volume, consumption **Related pages:** /a2p-messaging-api/http/dlrs, /a2p-messaging-api/http/rate-limits **Docs index (every page):** https://docs.instasent.com/llms.txt **This zone's index:** https://docs.instasent.com/a2p-messaging-api/llms-full.txt **This page:** https://docs.instasent.com/a2p-messaging-api/http/metrics/ (HTML) · https://docs.instasent.com/a2p-messaging-api/http/metrics.md (Markdown) [Delivery reports](/a2p-messaging-api/http/dlrs) tell you what happened to each message, one webhook call at a time. Metrics answer the aggregate questions in one request: how many messages you sent last week, how many were delivered, to which countries and networks, through which token, and what they cost. You get the figures without storing and counting every DLR yourself, which makes this the endpoint for usage dashboards, delivery-rate monitoring and cost reconciliation. ## Request [`GET /transactional/v1/sms/metrics` - SMS traffic metrics of your API tokens over a window of UTC days or months.](/a2p-messaging-api/http/reference) Authenticate with an `api_sms` token, the same one you send with (see [Authentication](/a2p-messaging-api/http/authentication)). The token you call with does not narrow the figures: any of your tokens reads the traffic of **all** your API tokens, deleted ones included, unless you pass `tokenId`. ```bash # September 2026, one row per day curl "https://api.instasent.com/transactional/v1/sms/metrics?from=2026-09-01&to=2026-09-30" \ -H "Authorization: Bearer $INSTASENT_TOKEN" # January to September 2026, one row per month, one token only curl "https://api.instasent.com/transactional/v1/sms/metrics?granularity=month&from=2026-01&to=2026-09&tokenId=66f1a2b3c4d5e6f7a8b9c0d1" \ -H "Authorization: Bearer $INSTASENT_TOKEN" ``` ### Query parameters - `from` — `string`, required First day of the window, included, as `YYYY-MM-DD` in UTC. With `granularity=month`, `YYYY-MM` is accepted too, and a full date is taken as its whole month. - `to` — `string`, required Last day of the window, included, in the same format as `from`. It must not be earlier than `from`. - `granularity` — `string`, default: `day` Size of each bucket: `day` or `month`. With `day` the window can span at most **92 days**; with `month`, at most **1100 days** (about three years). - `tokenId` — `string` Narrows the figures to one token. Use the `tokenId` returned in `breakdown.byToken`. A token that is unknown or sent nothing in the window returns zeros, not an error. ## What the figures count - **Only charged messages.** A message that was not charged does not appear here, so these figures are your billed traffic, not every request you made. See [What is billed](/a2p-messaging-api/channels/sms/senders#what-is-billed) for the cases where a message is blocked and not charged. - **By charge date, in UTC.** Each message lands in the UTC day (or month) it was charged, whatever your local time zone. A message charged at 00:30 on 1 October in Madrid (UTC+2) counts on 30 September. - **Test traffic is left out.** - **`cost` is what you were charged**, in EUR, rounded to four decimals. - **Each message is counted once, under its current status**: `delivered`, `failed`, `error`, `sent`, `enqueued`, `expired` or `rejected`. They mean the same as in the [DLR status list](/a2p-messaging-api/http/dlrs#status-list); `enqueued` is a message still waiting to be handed to the carrier. Statuses without a counter of their own (such as `buffered` or `accepted`) only add to `messages`, so the status counters need not add up to `messages`. - **`noDR` counts messages that have not received any delivery report.** It overlaps the status counters (a `sent` message with no report is in both), so never add it to them. To compute a delivery rate, divide `delivered` by `messages`. On the most recent buckets that ratio can still rise as late reports arrive (see below). ## Freshness: `dataUpTo` and `finalBefore` The figures are aggregated periodically, not computed in real time: a message you sent a minute ago is not in them yet. Two fields of every response tell you how far to trust each bucket. - **`dataUpTo`** is the instant (ISO 8601, UTC) up to which charged traffic is included. It is `null` when that instant is not known at the time of the call; the figures are returned anyway. - **`finalBefore`** is the first bucket that may still change, in the same format as the buckets (`YYYY-MM-DD` by day, `YYYY-MM` by month). Every bucket before it is final. Buckets from it on are provisional, because carriers keep sending delivery reports after the send, sometimes days later, and each one can move a message from `sent` to `delivered` or `failed`. > **Tip**: If you copy the figures into your own store, keep the buckets before `finalBefore` for good and read the ones from `finalBefore` on again in a later call. Read `finalBefore` on every response instead of assuming a fixed number of days, and use `dataUpTo` to decide whether there is anything new to fetch. ## Response A `200` returns the metrics in `entity`: ```json { "entity": { "channelType": "sms", "granularity": "day", "from": "2026-09-28", "to": "2026-09-30", "timezone": "UTC", "dataUpTo": "2026-09-30T22:00:00+00:00", "finalBefore": "2026-09-29", "totals": { "messages": 2000, "parts": 2170, "delivered": 1881, "failed": 27, "error": 3, "sent": 73, "enqueued": 3, "expired": 5, "rejected": 5, "noDR": 79, "cost": 86.8 }, "series": [ { "date": "2026-09-28", "messages": 1200, "parts": 1310, "delivered": 1150, "failed": 18, "error": 2, "sent": 21, "enqueued": 0, "expired": 4, "rejected": 3, "noDR": 23, "cost": 52.4 }, { "date": "2026-09-29", "messages": 0, "parts": 0, "delivered": 0, "failed": 0, "error": 0, "sent": 0, "enqueued": 0, "expired": 0, "rejected": 0, "noDR": 0, "cost": 0 }, { "date": "2026-09-30", "messages": 800, "parts": 860, "delivered": 731, "failed": 9, "error": 1, "sent": 52, "enqueued": 3, "expired": 1, "rejected": 2, "noDR": 56, "cost": 34.4 } ], "breakdown": { "byCountry": [ { "country": "ES", "messages": 1500, "parts": 1630, "delivered": 1412, "failed": 20, "error": 2, "sent": 55, "enqueued": 2, "expired": 4, "rejected": 3, "noDR": 59, "cost": 65.2 }, { "country": "PT", "messages": 500, "parts": 540, "delivered": 469, "failed": 7, "error": 1, "sent": 18, "enqueued": 1, "expired": 1, "rejected": 2, "noDR": 20, "cost": 21.6 } ], "byNetwork": [ { "network": "21407", "messages": 900, "parts": 980, "delivered": 848, "failed": 12, "error": 1, "sent": 33, "enqueued": 1, "expired": 3, "rejected": 2, "noDR": 35, "cost": 39.2 }, { "network": "21401", "messages": 600, "parts": 650, "delivered": 564, "failed": 8, "error": 1, "sent": 22, "enqueued": 1, "expired": 1, "rejected": 1, "noDR": 24, "cost": 26 }, { "network": "26806", "messages": 500, "parts": 540, "delivered": 469, "failed": 7, "error": 1, "sent": 18, "enqueued": 1, "expired": 1, "rejected": 2, "noDR": 20, "cost": 21.6 } ], "byToken": [ { "tokenId": "66f1a2b3c4d5e6f7a8b9c0d1", "name": "Production", "deleted": false, "messages": 1800, "parts": 1950, "delivered": 1690, "failed": 25, "error": 3, "sent": 69, "enqueued": 3, "expired": 5, "rejected": 4, "noDR": 75, "cost": 78 }, { "tokenId": "65a0b1c2d3e4f5a6b7c8d9e0", "name": "Old integration", "deleted": true, "messages": 200, "parts": 220, "delivered": 191, "failed": 2, "error": 0, "sent": 4, "enqueued": 0, "expired": 0, "rejected": 1, "noDR": 4, "cost": 8.8 } ] } } } ``` In this example the 28th is final, while the 29th and 30th may still change. The 29th had no traffic and still has its row. - `channelType` — `string` Always `sms`. - `granularity` — `string` `day` or `month`, as requested. - `from` — `string` First bucket of the window: `YYYY-MM-DD` by day, `YYYY-MM` by month (even if you passed a full date). - `to` — `string` Last bucket of the window, in the same format. - `timezone` — `string` Always `UTC`: the zone every bucket is cut in. - `dataUpTo` — `string | null` The instant up to which charged traffic is included, ISO 8601. `null` when unknown. - `finalBefore` — `string` The first bucket that may still change, in the bucket format; every earlier bucket is final. - `totals` — `object` The [counters](#counters) of the whole window. - `series` — `object[]` Every bucket of the window in time order, including those without traffic, which come at zero. Each item is a `date` (bucket format) plus the [counters](#counters). - `breakdown` — `object` The whole window split three ways. Rows are sorted by `messages`, busiest first, and each row carries its label plus the [counters](#counters). - `byCountry` — `object[]` `country`: ISO 3166-1 alpha-2 code of the destination (`ES`), or `null` when unknown. - `byNetwork` — `object[]` `network`: the destination mobile network as its MCC-MNC code in one string (`21407`), or `null` when unknown. - `byToken` — `object[]` `tokenId` (the value to pass as `tokenId`; it can be `null` for traffic not tied to a token), `name` (the token's name) and `deleted` (`true` when the token no longer exists; its past traffic still counts). ### Counters The same eleven counters appear in `totals`, in every `series` item and in every breakdown row. - `messages` — `integer` Charged messages. - `parts` — `integer` Charged message parts. A long message is split into several parts, each charged, and Unicode characters lower the length at which that happens. - `delivered` — `integer` Messages whose current status is `delivered`. - `failed` — `integer` Messages whose current status is `failed`. - `error` — `integer` Messages whose current status is `error`. - `sent` — `integer` Messages whose current status is `sent`: sent, with no final delivery report yet. - `enqueued` — `integer` Messages whose current status is `enqueued`: not yet handed to the carrier. - `expired` — `integer` Messages whose current status is `expired`. - `rejected` — `integer` Messages whose current status is `rejected`. - `noDR` — `integer` Messages that have not received any delivery report. Overlaps the status counters. - `cost` — `number` Amount charged, in EUR, rounded to four decimals. ## Errors and limits - **`400 Bad Request`** when `from` or `to` is missing or is not a valid date in the expected format (an impossible date such as `2026-02-30` included), when `from` is after `to`, when the window is wider than the granularity allows, or when `granularity` or `tokenId` is not a valid value. Fix the parameters; retrying the same request does not help. - **`404 Not Found`** when your account has no A2P Messaging API project. An unknown `tokenId` is not a `404`: it returns zeros. - **`401`, `403` and `429`** behave as everywhere else in the API; see [Errors](/a2p-messaging-api/http/errors). - **Rate limit: 10 requests per 60 seconds**, counted like the rest of the API's limits (see [Rate limits](/a2p-messaging-api/http/rate-limits)). Since the figures only refresh periodically, calling more often returns nothing new: cache the response and check `dataUpTo` before fetching again. ## What's next - **[Receiving DLRs](/a2p-messaging-api/http/dlrs)** — the per-message status, pushed to your webhook as it changes. - **[SMS senders](/a2p-messaging-api/channels/sms/senders)** — what is billed when a message is blocked. - **[Rate limits](/a2p-messaging-api/http/rate-limits)** — per-endpoint limits and the `X-RateLimit-*` headers. - **[API Reference](/a2p-messaging-api/http/reference)** — the full schema of `GET /sms/metrics`. --- This is one page of the Instasent documentation. For the complete machine-readable index of every guide and API reference, fetch https://docs.instasent.com/llms.txt — start there for full context. --- URL: https://docs.instasent.com/a2p-messaging-api/smpp/authentication # SMPP authentication The A2P Messaging 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. **Language:** en **Audience:** developer **TLDR:** SMPP is requested from the dashboard (SMS > Settings, Request SMPP opens the support chat) and needs two things: SMPP access, which Instasent enables, and the wildcard route for wholesale providers; without both, SMPP keys can't be created. An api_sms key with the SMPP scope then shows a system_id and password pair, and its HTTP token keeps working in parallel. Bind once to smpp.instasent.com port 2775 (SMPP v3.4 or v5.0) with bind_transceiver, bind_transmitter or bind_receiver, keep the session open and send enquire_link periodically. **Docs index (every page):** https://docs.instasent.com/llms.txt **This zone's index:** https://docs.instasent.com/a2p-messaging-api/llms-full.txt **This page:** https://docs.instasent.com/a2p-messaging-api/smpp/authentication/ (HTML) · https://docs.instasent.com/a2p-messaging-api/smpp/authentication.md (Markdown) 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 SMPP is not available by default: you **request it from the dashboard**, and it needs **two things** on your account: 1. **SMPP access.** In the A2P Messaging API project, the SMS channel's **Settings** tab has an **SMPP** block that shows whether it is on; you can't switch it yourself. While it is off, **Request SMPP** opens the support chat with your request, and Instasent enables SMPP for your organization. 2. **The [wildcard route](/a2p-messaging-api/channels/sms/wildcard-route).** SMPP traffic always carries your customer's own sender name in `source_addr`, so it relies on the route that carries senders not registered in your account. The route is for wholesale providers and is requested from the same **Settings** tab, with your wholesale legal profile. Both statuses are also in the **Wholesale providers** section of the SMS channel's right-hand panel, with a **Request** button on whichever is off. If SMPP is on but the wildcard route isn't, the **SMPP** block says that SMPP keys also need the route. **Without both, SMPP keys can't be created**, and so nothing can be sent over SMPP. On an API key, the **Send SMS through SMPP** option stays disabled, and its tooltip says what is missing: - Without SMPP access — *"Needs extra access for your organization. Contact us to enable it."* - Without the wildcard route — *"Needs the wildcard route. Request it in the SMS channel settings."*, with a **Request it in SMS settings** link under the options. A key that already has SMPP keeps it. Once both are in place, an `api_sms` key with **Send SMS through SMPP** exposes an extra pair of credentials — `systemID` and `password` — for use over SMPP. The key's 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](/a2p-messaging-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](/a2p-messaging-api/smpp/integration)** — PDUs supported, sending, concatenation, DLRs, inbound messages. --- This is one page of the Instasent documentation. For the complete machine-readable index of every guide and API reference, fetch https://docs.instasent.com/llms.txt — start there for full context. --- URL: https://docs.instasent.com/a2p-messaging-api/smpp/integration # Integration Supported PDUs, submit_sm fields, message concatenation, DLRs over deliver_sm and inbound handling for the A2P Messaging API over SMPP. **Language:** en **Audience:** developer **TLDR:** Send with submit_sm: source_addr of 3 to 11 characters, destination_addr in E.164, registered_delivery=1 to receive DLRs, and data_coding 0 (GSM7), 3 (Latin-9) or 8 (UCS2). For long texts use message_payload or UDHI segmentation (esm_class=0x40). DLRs arrive as deliver_sm with esm_class=4 in the usual id/stat/err format, and inbound messages as deliver_sm with esm_class=0. **Docs index (every page):** https://docs.instasent.com/llms.txt **This zone's index:** https://docs.instasent.com/a2p-messaging-api/llms-full.txt **This page:** https://docs.instasent.com/a2p-messaging-api/smpp/integration/ (HTML) · https://docs.instasent.com/a2p-messaging-api/smpp/integration.md (Markdown) Once the session is bound (see [Authentication](/a2p-messaging-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. | `source_addr` is always your customer's own sender name, carried on the account's [wildcard route](/a2p-messaging-api/channels/sms/wildcard-route) — SMPP keys exist only on accounts that have it (see [Enabling SMPP](/a2p-messaging-api/smpp/authentication#enabling-smpp-on-your-account)). Your project's [destination countries](/a2p-messaging-api/channels/sms/destination-countries) apply to SMPP traffic too. 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`. --- This is one page of the Instasent documentation. For the complete machine-readable index of every guide and API reference, fetch https://docs.instasent.com/llms.txt — start there for full context. --- URL: https://docs.instasent.com/a2p-messaging-api/sdks/node # Node.js SDK Official Node.js client for the Instasent A2P Messaging 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. **Language:** en **Audience:** developer **TLDR:** Install with npm install instasent, create an Instasent.SmsClient with your api_sms token and call sendSms(sender, to, text); getSms and getSmsById read messages. The SDK's canonical source is under review, so treat these method names as indicative and check the GitHub repository before production; anything else is a plain HTTP call against the API reference. **Docs index (every page):** https://docs.instasent.com/llms.txt **This zone's index:** https://docs.instasent.com/a2p-messaging-api/llms-full.txt **This page:** https://docs.instasent.com/a2p-messaging-api/sdks/node/ (HTML) · https://docs.instasent.com/a2p-messaging-api/sdks/node.md (Markdown) The Node.js SDK wraps the A2P Messaging 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](/a2p-messaging-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. --- This is one page of the Instasent documentation. For the complete machine-readable index of every guide and API reference, fetch https://docs.instasent.com/llms.txt — start there for full context. --- URL: https://docs.instasent.com/a2p-messaging-api/sdks/php # PHP SDK Official PHP client for the Instasent A2P Messaging 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. **Language:** en **Audience:** developer **TLDR:** Install with composer require instasent/instasent-php-lib (or from the GitHub ZIP) and use SmsClient with your api_sms token: sendSms, sendUnicodeSms for text outside GSM-7, and getSmsById. The same package has LookupClient (doLookup) and VerifyClient, which requests a code (requestVerify) and checks it (checkVerify). Requires PHP 5.2.3 or later. **Docs index (every page):** https://docs.instasent.com/llms.txt **This zone's index:** https://docs.instasent.com/a2p-messaging-api/llms-full.txt **This page:** https://docs.instasent.com/a2p-messaging-api/sdks/php/ (HTML) · https://docs.instasent.com/a2p-messaging-api/sdks/php.md (Markdown) 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). --- This is one page of the Instasent documentation. For the complete machine-readable index of every guide and API reference, fetch https://docs.instasent.com/llms.txt — start there for full context. --- URL: https://docs.instasent.com/a2p-messaging-api/sdks/python # Python SDK Official Python client for the Instasent A2P Messaging API. Install via pip, instantiate the client with your api_sms token, and call send_sms. **Language:** en **Audience:** developer **TLDR:** Install with pip install instasent (or python setup.py install from source), create instasent.Client with your api_sms token and call send_sms(sender, to, text); get_sms and get_sms_by_id read messages. Anything beyond these methods is a plain REST call against the API reference. **Docs index (every page):** https://docs.instasent.com/llms.txt **This zone's index:** https://docs.instasent.com/a2p-messaging-api/llms-full.txt **This page:** https://docs.instasent.com/a2p-messaging-api/sdks/python/ (HTML) · https://docs.instasent.com/a2p-messaging-api/sdks/python.md (Markdown) Minimal client over the A2P Messaging HTTP endpoints. Anything beyond the methods below is a plain REST call — the [Reference](/a2p-messaging-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). --- This is one page of the Instasent documentation. For the complete machine-readable index of every guide and API reference, fetch https://docs.instasent.com/llms.txt — start there for full context. --- URL: https://docs.instasent.com/a2p-messaging-api/sdks/ruby # Ruby SDK Official Ruby client for the Instasent A2P Messaging API. Install the instasent gem, instantiate Instasent::Client with your api_sms token, and call send_sms. **Language:** en **Audience:** developer **TLDR:** Install the instasent gem (gem install instasent, or add it to your Gemfile and run bundle install), create Instasent::Client with your api_sms token and call send_sms(sender, to, text); get_sms and get_sms_by_id read messages. Anything else is a plain HTTP call against the API reference. **Docs index (every page):** https://docs.instasent.com/llms.txt **This zone's index:** https://docs.instasent.com/a2p-messaging-api/llms-full.txt **This page:** https://docs.instasent.com/a2p-messaging-api/sdks/ruby/ (HTML) · https://docs.instasent.com/a2p-messaging-api/sdks/ruby.md (Markdown) Minimal client over the A2P Messaging HTTP endpoints. For anything outside the methods below, drop back to a plain HTTP call — the [Reference](/a2p-messaging-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). --- This is one page of the Instasent documentation. For the complete machine-readable index of every guide and API reference, fetch https://docs.instasent.com/llms.txt — start there for full context. --- URL: https://docs.instasent.com/a2p-messaging-api/sdks/java # Java SDK Official Java client for the Instasent A2P Messaging API. Download the JAR, construct an InstasentClient with your api_sms token, and call sendSms. **Language:** en **Audience:** developer **TLDR:** Download the instasent-java-lib JAR (or the JAR with dependencies) and add it to your classpath, create an InstasentClient with your api_sms token and call sendSms(sender, to, text, callback); getSms and getSmsById read messages. Anything else is a plain HTTP call against the API reference. **Docs index (every page):** https://docs.instasent.com/llms.txt **This zone's index:** https://docs.instasent.com/a2p-messaging-api/llms-full.txt **This page:** https://docs.instasent.com/a2p-messaging-api/sdks/java/ (HTML) · https://docs.instasent.com/a2p-messaging-api/sdks/java.md (Markdown) Thin client over the A2P Messaging HTTP endpoints. For anything outside the methods below, use a plain HTTP client — the [Reference](/a2p-messaging-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). --- This is one page of the Instasent documentation. For the complete machine-readable index of every guide and API reference, fetch https://docs.instasent.com/llms.txt — start there for full context.