top of page

Bulk SMS API for Developers in Bangalore: Integration Patterns, DLT Constraints, and Production Practices

Jul 11, 2025
15 min read

Updated: Sep 28

By TechTo Networks · Originally published July 11, 2025 · Reviewed and updated periodically

If you are building a product in Bangalore -or anywhere else in India -and need to send OTPs, transactional alerts, or bulk notifications by SMS, the technical question is rarely "how do I make one API call." It is "what do I build around that call so it survives launch day." This guide covers that layer: how TechTo's SMS API actually works, how to structure your backend around it, how Indian DLT rules show up in your code, and what to check before going live.

A note on location, since it is in the title: TechTo Networks is headquartered in Thiruvananthapuram, Kerala. The API is location-independent, and onboarding is remote, so a team in Bangalore integrates the same way a team in Pune or Chennai does. What is specific to Bangalore is the ecosystem: fast-moving startup stacks (Django, Node, Go, PHP, and mixed microservices), heavy reliance on OTP-based signup, and a large Kannada-speaking user base that makes regional-language SMS a real requirement rather than a checkbox.

For exact parameter syntax, keep TechTo's HTTP API reference open alongside this page. For the simplest possible first call, see how to send an SMS via API. For OTP category rules, see the OTP SMS API guide.

Neon icon with "SMS" text centered. Background shows outlines of Bangalore landmarks. Text reads "BULK SMS IN BANGALORE, TECHTO NETWORKS."

What the API Actually Looks Like

Developers coming from Twilio-style services often expect a JSON REST API with an SDK. TechTo's API is different, and it is better to know that before you write any code.

TechTo's SMS API is an HTTP GET, query-parameter API, offered in three variants:

  • Tally HTTP API — authenticates with a username and password passed as query parameters.

  • Token-based HTTP API — authenticates with an account token, adds a templateid parameter for your DLT-approved template, and offers JSON responses for delivery-report and credit-balance lookups.

  • XML API — the same parameters wrapped in an XML payload, URL-encoded, and sent as a GET request.

The documented Tally request looks like this:

Shared parameters across the variants:

Parameter

Meaning

sender

Your DLT-registered sender ID (header)

number

Recipient number; comma-separated for identical-message bulk sends

route

Promotional (1), Transactional (2), Sender ID (3), Trans OTP (4, token/XML only), International (9), Trans2 (10)

type

Text (1), Flash (2), Unicode (3)

sms

Message text, URL-encoded

templateid

DLT template ID (required on the token and XML variants)

Three things follow from this design, and each one changes how you build:

  1. There is no official SDK to install. TechTo's documented integration is plain HTTP. A thin wrapper of a few dozen lines in your own codebase is all you need. If you see a tutorial telling you to npm install or pip install a TechTo package, do not run it unless TechTo confirms in writing that the package is theirs.

  2. Responses are plain text on the send call. A success returns a numeric message ID; a failure returns a numeric error code. Your parser has to tell the two apart.

  3. No scheduling parameter and no webhook parameter are documented. Scheduling, retries, and delivery-status tracking are things your backend owns. That sounds like a limitation; in practice it means the behavior is under your control and easy to reason about.

Make Your First Call from a Backend, Not a Client

Before code: never call this API from a mobile app, a browser, or any client you distribute. The Tally variant puts your username and password in the query string; the token variant puts a token there. Either way, anything shipped in an app binary or a web bundle can be extracted. The correct shape is:

  1. The client asks your server to send an OTP or notification.

  2. Your server validates the request, applies rate limits, and enqueues a job.

  3. A worker calls TechTo's API with credentials that never leave your infrastructure.

  4. The worker records the returned message ID against your own business record.

Python

import os
import requests

TALLY_URL = "http://godspeed.liveair.co.in/httpapi/tally"

# Documented error codes for the send call (see the table below)
ERROR_CODES = {
    "101": "Invalid user", "102": "Invalid sender ID", "103": "Invalid contact(s)",
    "104": "Invalid SMS route", "105": "Invalid message type", "106": "Message content missing",
    "107": "Spam blocked", "108": "Low credits on route", "109": "No SMSC",
    "110": "Promotional route works 9am-9pm only", "111": "Connection error",
    "112": "All numbers are DND", "113": "Invalid template id",
}

def send_sms(number: str, text: str, sender: str, route: int = 2, msg_type: int = 1):
    params = {
        "username": os.environ["TECHTO_USERNAME"],
        "password": os.environ["TECHTO_PASSWORD"],
        "sender": sender,
        "number": number,          # confirm 10-digit vs 91-prefixed format for your account
        "route": route,
        "type": msg_type,
        "sms": text,               # requests URL-encodes this for you
    }
    resp = requests.get(TALLY_URL, params=params, timeout=10)
    resp.raise_for_status()
    body = resp.text.strip()
    if body in ERROR_CODES:
        return {"ok": False, "code": body, "reason": ERROR_CODES[body]}
    return {"ok": True, "message_id": body}

Node.js (18+)

const TALLY_URL = "http://godspeed.liveair.co.in/httpapi/tally";

const ERROR_CODES = new Set([
  "101","102","103","104","105","106","107","108","109","110","111","112","113",
]);

async function sendSms({ number, text, sender, route = 2, type = 1 }) {
  const params = new URLSearchParams({
    username: process.env.TECHTO_USERNAME,
    password: process.env.TECHTO_PASSWORD,
    sender, number, route: String(route), type: String(type), sms: text,
  });
  const res = await fetch(`${TALLY_URL}?${params}`, { signal: AbortSignal.timeout(10_000) });
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  const body = (await res.text()).trim();
  if (ERROR_CODES.has(body)) return { ok: false, code: body };
  return { ok: true, messageId: body };
}

PHP

<?php
function techto_send(string $number, string $text, string $sender, int $route = 2, int $type = 1): array {
    $query = http_build_query([
        'username' => getenv('TECHTO_USERNAME'),
        'password' => getenv('TECHTO_PASSWORD'),
        'sender' => $sender, 'number' => $number,
        'route' => $route, 'type' => $type, 'sms' => $text,
    ]);
    $ch = curl_init("http://godspeed.liveair.co.in/httpapi/tally?$query");
    curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 10]);
    $body = trim((string) curl_exec($ch));
    curl_close($ch);
    $errors = ['101','102','103','104','105','106','107','108','109','110','111','112','113'];
    return in_array($body, $errors, true)
        ? ['ok' => false, 'code' => $body]
        : ['ok' => true, 'message_id' => $body];
}

Two cautions on these samples. First, the documented Tally URL has no templateid parameter, so on that variant the message text itself must match an approved DLT template exactly. On the token and XML variants, pass the templateid. Second, the token-variant endpoint paths are on the API reference page; copy them from there rather than from any blog post, including this one.

Handling Responses: Which Errors Are Retryable

The most useful thing you can do in your integration is classify errors, because retrying the wrong ones wastes credits and retrying none of them loses messages. Based on the documented codes:

Code

Meaning

What your code should do

101

Invalid user

Do not retry. Alert: credentials are wrong or revoked.

102

Invalid sender ID

Do not retry. Configuration or DLT registration problem.

103

Invalid contact(s)

Do not retry. Mark the number invalid in your database.

104

Invalid SMS route

Do not retry. Fix the route value.

105

Invalid message type

Do not retry. Fix the type value.

106

Message content missing

Do not retry. Bug in message construction.

107

Spam blocked

Do not retry. Review content and template; escalate to TechTo if it is a legitimate message.

108

Low credits on route

Pause the queue for that route and alert. Retrying burns attempts, not credits.

109

No SMSC

Retry with backoff; escalate if it persists.

110

Promotional route only works 9 AM–9 PM

Do not retry now. Re-queue for the next allowed window.

111

Connection error

Retry with exponential backoff.

112

All numbers are DND (token variant)

Do not retry. Suppress and record.

113

Invalid template ID (token variant)

Do not retry. Template mismatch or wrong ID.

Two subtleties. A message ID and an error code are both numeric, so match against the documented set of codes rather than checking "is it a number." And a network timeout is ambiguous: the request may have reached TechTo and been accepted even though you never saw the reply. That leads directly to the next section.

Idempotency Is Your Job

Because there is no documented idempotency key on this API, protect yourself against duplicate sends on your side. The pattern that works:

  1. Before calling the API, write a row such as (business_event_id, channel='sms', status='pending') with a unique constraint on business_event_id.

  2. Call the API. On success, update the row with the message ID and status='sent'.

  3. On an ambiguous failure (timeout, dropped connection), do not blindly resend. Mark the row unknown, and either query delivery status by your own reference or accept a small, bounded duplicate risk for non-critical messages.

  4. For OTPs, a duplicate is harmless (the newer code simply replaces the older one). For payment alerts or order confirmations, prefer "unknown → manual/automated reconcile" over "unknown → resend."

This is the same discipline you would apply to any external side effect, and it is easy to skip when an API looks simple.

DLT Constraints as They Appear in Code

India's DLT framework is the biggest difference between integrating an SMS API here and integrating one elsewhere. It is enforced by the telecom operators, not by your gateway, so no code change can bypass it. What matters to a developer:

Sender IDs, templates, and entity registration are prerequisites. Under TRAI's Telecom Commercial Communications Customer Preference Regulations, 2018 (TCCCPR), a business registers as a Principal Entity, registers sender headers, and registers each message template. TechTo's onboarding team assists with this; the DLT and TRAI regulations resource covers the process. Do it early. Approval timing varies, and it is the most common cause of a slipped launch date.

A template is a contract, and your code must honor it. The fixed text has to match exactly. That means your message-building function should render text from a single source of truth (a constants file or a template table), not from strings scattered across services. A stray space or a reworded sentence in one microservice is enough to get messages blocked.

Variables must now be typed. TRAI has moved from free-form {#var#} placeholders to pre-tagged variables. A TRAI direction dated 18 November 2025 under TCCCPR 2018 requires each variable in a content template to carry one of six approved tags — numeric, alphanumeric, URL, OTT link, callback number, or email — with new-template enforcement starting in mid-January 2026 and a transition window for existing templates, as reported by DLT platforms and industry notices. Earlier TRAI directions (2023) had already required variables to be pre-tagged, limited templates to three variable parts unless specially justified, and required that at least 30 percent of a template be fixed text (PIB summary of the 2023 direction). For developers this has concrete consequences:

  • An OTP variable should only ever receive digits. If your code can put anything else into a numeric-tagged slot, the message can be rejected.

  • Names go into alphanumeric slots; do not inject links into them.

  • Any URL in a message must come from a whitelisted domain. If your app sends tracking links, use a domain you have registered and whitelisted, and generate the path portion only. Dynamic third-party shorteners are a frequent source of rejections.

  • Keep templates short on variables. Three or fewer is the safe design target; the fixed-text ratio matters too.

  • Treat the regulator's transition windows as passed. As of this review, assume untagged templates are non-compliant and check your DLT portal for any still flagged.

The regulatory detail continues to move. The TRAI direction page and your DLT portal are the sources of truth; this page reflects the position at the time of review.

Headers carry a category suffix. Sender headers now show a category marker (-P, -S, -T, -G) so recipients can see whether a message is promotional, service, transactional, or government. Register the right header for the right category, and do not reuse a transactional header for marketing.

Route matters. Use the Transactional or OTP route for account and service messages, and the Promotional route only for marketing. Promotional messages are filtered against DND and restricted to 9 AM–9 PM (the API returns code 110 outside the window). Sending marketing text through a transactional route to avoid DND is a violation that can get a header blocked.

Testing Without a Sandbox

TechTo does not document a sandbox that simulates carriers, so do not design your test plan around one. A practical alternative:

  • Register one test template and one test sender early, with harmless text.

  • Send to a small list of numbers your team controls across at least Jio, Airtel, and Vi (and BSNL if you have a user base there), on real handsets.

  • Put the API call behind an interface in your code so unit tests and staging can use a fake sender, and only a dedicated "smoke test" job touches the real API.

  • Test failure paths deliberately: an unregistered sender (code 102), a bad template (107 or 113 depending on variant), an invalid number (103), and a wrong password (101). Confirm your alerting fires.

This costs a few credits and saves a production incident.

Architecture: Queue, Worker, Outbox

For anything beyond a hobby project, send SMS from a worker, not from the request thread. A request that waits ten seconds on an external HTTP call is a request that times out under load.

Django with Celery

# tasks.py
from celery import shared_task
from .sms import send_sms  # the function shown earlier

RETRYABLE = {"109", "111"}

@shared_task(bind=True, max_retries=5)
def send_sms_task(self, event_id, number, text, sender, route):
    result = send_sms(number, text, sender, route=route)
    if result["ok"]:
        record_sent(event_id, result["message_id"])
        return result["message_id"]
    if result["code"] in RETRYABLE:
        raise self.retry(countdown=2 ** self.request.retries * 5)
    if result["code"] == "108":
        pause_route(route)
    record_failed(event_id, result["code"])

Node.js with BullMQ

const { Queue, Worker } = require("bullmq");
const smsQueue = new Queue("sms", { connection: redis });

async function enqueue(job) {
  await smsQueue.add("send", job, {
    jobId: job.eventId,                // dedupes on your business event
    attempts: 5,
    backoff: { type: "exponential", delay: 5000 },
    removeOnComplete: 1000,
  });
}

new Worker("sms", async (j) => {
  const r = await sendSms(j.data);
  if (r.ok) return r.messageId;
  if (["109", "111"].includes(r.code)) throw new Error(`retryable ${r.code}`);
  await recordFailure(j.data.eventId, r.code);   // do not retry permanent errors
}, { connection: redis, concurrency: Number(process.env.SMS_CONCURRENCY || 5) });

Note the concurrency setting reads from configuration. TechTo has not published a per-account throughput figure on this page, so do not hardcode one. Ask TechTo what concurrency and messages-per-second your account supports, start conservatively, and raise it in staged steps while watching error codes.

Configuration per environment

Keep credentials, sender IDs, and template IDs in environment variables or a secrets manager, one set per environment. Template IDs in particular change when a template is re-registered; making them configuration means a template change is a config push, not a code deploy. Never share one credential set between staging and production.

Building a Correct OTP Flow

OTP is where Bangalore products feel SMS problems first, because signup and login depend on it. TechTo's API delivers the message; your backend owns generation, storage, expiry, and verification, since the documented API has no verify endpoint. A sound design:

  1. Generate the code with a cryptographically secure random source (not Math.random).

  2. Store a hash, not the plaintext, with an expiry (typically a few minutes), an attempt counter, and a resend cooldown.

  3. Send through the OTP route using a template whose variable is numeric-tagged.

  4. Verify by hashing what the user typed and comparing in constant time; cap attempts; invalidate on success.

  5. Rate limit by phone number and by IP or device, to prevent someone from using your OTP endpoint to flood a stranger's phone (and your credit balance).

const crypto = require("crypto");

async function requestOtp(phone) {
  if (await redis.exists(`otp:cooldown:${phone}`)) throw new Error("Try again shortly");
  const code = String(crypto.randomInt(100000, 1000000));
  const hash = crypto.createHmac("sha256", process.env.OTP_SECRET).update(`${phone}:${code}`).digest("hex");
  await redis.multi()
    .set(`otp:hash:${phone}`, hash, "EX", 300)
    .set(`otp:tries:${phone}`, 0, "EX", 300)
    .set(`otp:cooldown:${phone}`, 1, "EX", 30)
    .exec();
  await enqueue({ eventId: `otp:${phone}:${Date.now()}`, number: phone, route: 4 /* Trans OTP, token/XML variant */,
                  text: renderOtpTemplate(code) });
}

async function verifyOtp(phone, entered) {
  const tries = await redis.incr(`otp:tries:${phone}`);
  if (tries > 5) return false;
  const expected = await redis.get(`otp:hash:${phone}`);
  if (!expected) return false;
  const actual = crypto.createHmac("sha256", process.env.OTP_SECRET).update(`${phone}:${entered}`).digest("hex");
  const ok = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(actual));
  if (ok) await redis.del(`otp:hash:${phone}`);
  return ok;
}

Route 4 (Trans OTP) is documented for the token and XML variants; on the Tally variant, use the transactional route your account is configured for. For the compliance side of OTP messaging — categories, template design, and what belongs on an OTP route — see the OTP SMS API guide.

Rather than promise a delivery time, measure your own. Log the time from "OTP requested" to "user submitted a correct code," and alert when the median or the 95th percentile drifts from your own baseline. That number reflects what your users experience, including carrier and handset effects that no vendor figure can capture.

Bulk and Personalized Sends

The number parameter accepts a comma-separated list, but the message text is the same for everyone in that call. That suits announcements. It does not suit personalized messages (different name, order ID, or amount per recipient). For those, loop over recipients and make one call each, through your queue, with bounded concurrency.

A few practical rules for larger campaigns:

  • Segment before you send. Remove invalid numbers, duplicates, and landlines. Strip prefixes consistently. A clean list improves your delivery numbers and cuts wasted credits.

  • Respect the promotional window in your scheduler. Since the API has no scheduling parameter, schedule from your side and gate promotional jobs to the 9 AM–9 PM window in India time. Code 110 is your safety net, not your plan.

  • Send in batches, not a burst. Feed the queue steadily and watch for a rise in 108, 109, or 111 responses as a signal to slow down.

  • Check credits before a run. On the token variant, the credits lookup returns the balance by route; a pre-flight check prevents a campaign from stalling halfway.

  • Keep transactional traffic separate. Do not let a large promotional run delay OTP jobs; give them a separate queue and a higher priority.

Regional Language and Unicode (Including Kannada)

For users who read Kannada, Hindi, Tamil, or other Indian scripts, send Unicode messages with type=3. Unicode segments hold 70 characters instead of the 160 available to standard GSM text, so a message that fits in one segment in English may become two or three in Kannada, and costs scale accordingly. Test with your longest realistic variable values, not with placeholders.

Two points need confirmation for your account because the documentation does not spell them out here: whether the sms parameter should carry URL-encoded UTF-8 (which is what standard libraries produce) or a different encoding, and how your DLT template for regional-language content must be registered. Confirm both with TechTo, then verify on real handsets before you rely on a regional-language flow.

Delivery Reports and Monitoring

Without documented webhooks, delivery tracking is a polling problem, and it deserves a design rather than a loop.

On the token variant, the delivery-report lookup takes your token and a message ID and returns JSON of the form [{"messageid": "...", "message": "...", "numbers": [["Number", "Status", "Time"], ...]}]. A reasonable pattern:

  • After a send, schedule a first status check after a short delay, then back off (for example, 30 seconds, 2 minutes, 10 minutes, then stop). Do not poll every message every second.

  • Store the final status and timestamp against your own record.

  • Treat "no final status after your polling window" as its own state, not as failure.

def check_report(message_id: str) -> list:
    r = requests.get(DLR_URL, params={"token": os.environ["TECHTO_TOKEN"], "messageid": message_id}, timeout=10)
    r.raise_for_status()
    return r.json()   # DLR_URL: copy the exact path from the API reference

What to monitor once you are live:

Signal

Why it matters

Send-call error-code mix (101–113)

A sudden rise in one code tells you which layer broke

Credit balance by route

Prevents a silent stall

Delivered vs. failed ratio, by route

Separates a template problem from a list-quality problem

Your own OTP request→verified time

The user-facing measure of OTP health

Queue depth and age

Shows backlog before users complain

Alert on changes against your own baseline rather than on numbers copied from a vendor page.

Security Checklist

  • Credentials in a secrets manager or environment variables; never in source control, client code, or logs.

  • Mask query strings in logs. With a GET API, your username, password, or token sits in the URL. Make sure HTTP client logging, reverse proxies, and APM tools redact it.

  • Ask about HTTPS and IP allow-listing. The documented endpoints are plain http://. Ask TechTo whether an HTTPS endpoint is available for your account and whether access can be restricted to your server IPs. If not, treat credentials as exposed to anything on the network path and keep sends inside a trusted network segment.

  • Prefer the token variant over shared username and password where you can, and rotate the token if it may have leaked.

  • Rate limit OTP requests per phone number and per client; add CAPTCHA or device checks where abuse is likely.

  • Validate phone numbers server-side before enqueueing.

  • Never expose raw API errors to end users.

  • Never install unofficial "TechTo SDK" packages from a public registry.

Go-Live Checklist

  1. DLT entity, headers, and templates registered and approved; variables typed correctly; URLs whitelisted.

  2. Test sends verified on real handsets across operators.

  3. Error classification, retries, and route pausing implemented and tested.

  4. Idempotency records in place for critical messages.

  5. Separate queues for OTP and promotional traffic.

  6. Credit-balance and queue-depth alerts configured.

  7. Log redaction confirmed.

  8. Throughput, support hours, and escalation contacts confirmed with TechTo in writing.

  9. A rollback plan: what your product does if SMS is unavailable (for example, a fallback verification method for a short period).

Pricing and Support

Current per-message rates by route are on the pricing page; check there rather than relying on figures in any article. When you compare quotes, ask for the effective cost per delivered message at your expected volume, including any DLT-related or GST charges, and check credit-validity terms.

For a team integrating from Bangalore, onboarding and support are handled remotely from TechTo's Thiruvananthapuram base. Confirm the support hours and channels that apply to your plan, and ask who to contact for an out-of-hours production issue before you need to.

Frequently Asked Questions

Does TechTo have an SDK for Node, Python, PHP, or Java?

TechTo's documented integration is plain HTTP, and none of these examples require an SDK. Do not install packages called techto-sms or similar from public registries unless TechTo confirms it publishes them.

Is there a sandbox for testing?

No sandbox is documented. Test with a small set of real numbers, a test template, and a fake sender in your code for unit tests.

Can I get delivery receipts by webhook?

No webhook parameter is documented. Use the delivery-report lookup on the token variant with a backoff schedule, and confirm with TechTo whether any push mechanism is available on your plan.

Can I schedule messages through the API?

No scheduling parameter is documented. Schedule in your own queue or scheduler, and keep promotional jobs inside the 9 AM–9 PM window.

Can I send different messages to many recipients in one call?

No. The comma-separated number field sends the same text to all. Personalized messages need one call per recipient.

What happens if my message does not match the template?

It is blocked by the operator's validation. Render message text from a single source of truth, and re-check variable tags and whitelisted URLs.

Does the API verify OTPs for me?

No. Your backend generates, stores, and verifies the code; the API only delivers it.

Can I send SMS outside India?

The documented routes include an International route (9), but Indian DLT rules apply to domestic traffic and international delivery has its own requirements. Confirm scope and pricing with TechTo before building on it.

What's Changed

This page is reviewed periodically. This revision replaced an earlier version that documented an API, SDKs, webhooks, and a sandbox that do not correspond to TechTo's published HTTP API, and removed unverified performance and customer claims. The DLT section now reflects TRAI's pre-tagging direction for template variables; regulatory details continue to change, so check the DLT and TRAI regulations resource and your DLT portal for current requirements.

Getting Started

Read the HTTP API reference, create an account, and complete DLT onboarding first, since template approval is the step most likely to set your launch date. Then build the queue, the error classification, and the OTP flow described above, test on real handsets, and go live with monitoring in place.

Comments

Rated 0 out of 5 stars.
No ratings yet

Add a rating
bottom of page