Powerful & Simple REST API

Integrate, Automate, and Deliver with Confidence.

One URL, one token, plain JSON. Send messages, follow their delivery, check prices and your balance, clean your lists with the opt-out blacklist, verify numbers with HLR and test real-world delivery, all from your own code. Every call is explained below with what it does, what it costs and what you get back.

API Playground

REQUEST

{
    "token": "YOUR-API-TOKEN-XXXX-XXXX-XXXX-XXXX-XXXX",
    "status": "account"
}
{
    "token": "YOUR-API-TOKEN-XXXX-XXXX-XXXX-XXXX-XXXX",
    "status": "token_ips",
    "limit": 20
}
{
    "token": "YOUR-API-TOKEN-XXXX-XXXX-XXXX-XXXX-XXXX",
    "send": "topup",
    "amount": 50
}
{
    "token": "YOUR-API-TOKEN-XXXX-XXXX-XXXX-XXXX-XXXX",
    "send": "bulk",
    "to": "34600111222,33612345678",
    "sender_id": "MyBrand",
    "message_content": "Hi #RND, your code is 1234.\nThank you.",
    "unicode": false
}
{
    "token": "YOUR-API-TOKEN-XXXX-XXXX-XXXX-XXXX-XXXX",
    "send": "dlr",
    "batch_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "summary": false
}
{
    "token": "YOUR-API-TOKEN-XXXX-XXXX-XXXX-XXXX-XXXX",
    "send": "prices"
}
{
    "token": "YOUR-API-TOKEN-XXXX-XXXX-XXXX-XXXX-XXXX",
    "send": "getoptions"
}
{
    "token": "YOUR-API-TOKEN-XXXX-XXXX-XXXX-XXXX-XXXX",
    "status": "my_routes"
}
{
    "token": "YOUR-API-TOKEN-XXXX-XXXX-XXXX-XXXX-XXXX",
    "send": "set_route",
    "option": "5d7d083aa9fbdf46fb90363937f8a8f3"
}
{
    "token": "YOUR-API-TOKEN-XXXX-XXXX-XXXX-XXXX-XXXX",
    "send": "delete_route",
    "prefix": "34"
}
{
    "token": "YOUR-API-TOKEN-XXXX-XXXX-XXXX-XXXX-XXXX",
    "status": "blacklist",
    "country": "ES"
}
{
    "token": "YOUR-API-TOKEN-XXXX-XXXX-XXXX-XXXX-XXXX",
    "send": "hlr",
    "to": "+34600111222, 33612345678"
}
{
    "token": "YOUR-API-TOKEN-XXXX-XXXX-XXXX-XXXX-XXXX",
    "status": "hlr",
    "id": 551
}
{
    "send": "get_available_networks"
}
{
    "token": "YOUR-API-TOKEN-XXXX-XXXX-XXXX-XXXX-XXXX",
    "send": "create_test",
    "networks": [
        { "mccmnc": "21407" },
        { "mccmnc": "20810" }
    ],
    "callbackUrl": "https://your-server.com/testsms-callback"
}
{
    "token": "YOUR-API-TOKEN-XXXX-XXXX-XXXX-XXXX-XXXX",
    "status": "get_sms_test_result",
    "id": 150917489
}

RESPONSE

{
    "balance": "541.58",
    "info": "CONNECTED_API",
    "username": "MYUSER",
    "user_id": 261,
    "role": "user",
    "is_admin": false,
    "token_activity": {
        "last_ip": "203.0.113.10",
        "last_user_agent": "curl/8.21.0",
        "last_used_at": "2026-09-04 03:02:11",
        "uses": 5739,
        "distinct_ips": 5
    }
}
{
    "info": "TOKEN_IPS",
    "activity": {
        "last_ip": "203.0.113.10",
        "last_user_agent": "curl/8.21.0",
        "last_used_at": "2026-09-04 03:06:38",
        "uses": 5765,
        "distinct_ips": 5
    },
    "ips": [
        {
            "ip": "203.0.113.10",
            "user_agent": "curl/8.21.0",
            "requests": 2,
            "first_seen": "2026-09-03 01:16:06",
            "last_seen": "2026-09-04 03:02:11"
        },
        {
            "ip": "198.51.100.7",
            "user_agent": "Ankarex-Telegram-Bot/1.0",
            "requests": 23,
            "first_seen": "2026-09-03 04:35:55",
            "last_seen": "2026-09-04 03:01:08"
        }
    ]
}
{
    "info": "TOPUP_LINK_CREATED",
    "url": "https://.../checkout/...",
    "direct": true,
    "expires_at": 1756955000,
    "expires_in": 1800,
    "balance": 541.46,
    "payment_id": "...",
    "amount": 50,
    "currency": "EUR"
}
{
    "info": "QUEUED",
    "batch_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "campaigns_count": 2,
    "numbers_count": 2,
    "estimated_cost": 0.1,
    "balance": 541.46,
    "rejected": {
        "invalid_format": 0,
        "no_route": 0
    }
}
{
    "status": 1,
    "batch_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "total_messages": 2,
    "summary": {
        "delivered": 1,
        "failed": 0,
        "sending": 1,
        "expired": 0,
        "delivery_rate": 50
    },
    "messages": [
        {
            "id": 1759619,
            "msisdn": "34600111222",
            "sender_id": "MyBrand",
            "status": "sent",
            "dlr_status": "DELIVERED",
            "dlr_received_at": "2026-09-04 03:10:12",
            "country": "Spain",
            "country_code": "ES",
            "mcc": "214",
            "submitted_at": "2026-09-04 03:09:58"
        },
        {
            "id": 1759620,
            "msisdn": "33612345678",
            "sender_id": "MyBrand",
            "status": "queued",
            "dlr_status": "PENDING",
            "dlr_received_at": null,
            "country": "France",
            "country_code": "FR",
            "mcc": "208",
            "submitted_at": "2026-09-04 03:09:58"
        }
    ],
    "success": 1,
    "fail": 0,
    "deliverSuc": 1,
    "deliverFail": 0,
    "deliverTimeout": 0
}
{
    "data": [
        {
            "name": "Spain",
            "prefix": "34",
            "price": 0.05,
            "type": "OPEN SID",
            "special_rate": false,
            "special_rate_price": null
        },
        {
            "name": "Belgium",
            "prefix": "32",
            "price": 0.075,
            "type": "SHORTCODE",
            "special_rate": false,
            "special_rate_price": null
        },
        {
            "name": "Mexico",
            "prefix": "52",
            "price": 0.014,
            "type": "LLC",
            "special_rate": true,
            "special_rate_price": 0.012
        }
    ]
}
{
    "options": {
        "AUSTRIA": [
            {
                "option_id": "cbc1501ae5ace16f333d7fd0e84754da",
                "name": "CH1",
                "provider": "CH1",
                "country": "Austria",
                "country_code": "AT",
                "price": 0.045,
                "prefix": "43",
                "type": "OPEN SID",
                "implementation": "53"
            },
            {
                "option_id": "5d7d083aa9fbdf46fb90363937f8a8f3",
                "name": "CH4",
                "provider": "CH4",
                "country": "Austria",
                "country_code": "AT",
                "price": 0.045,
                "prefix": "43",
                "type": "OPEN SID",
                "implementation": "22"
            }
        ],
        "SPAIN": [
            {
                "option_id": "9a1f0c2e7d3b4a5c6e7f8091a2b3c4d5",
                "name": "CH4",
                "provider": "CH4",
                "country": "Spain",
                "country_code": "ES",
                "price": 0.05,
                "prefix": "34",
                "is_custom": true
            }
        ]
    }
}
{
    "info": "MY_ROUTES",
    "routes": [
        {
            "id": 4821,
            "provider": "CH4",
            "channel": "CH4",
            "country": "Spain",
            "prefix": "34",
            "price": 0.05,
            "by_admin": false
        }
    ]
}
{
    "info": "ROUTE_SET",
    "operation": "created",
    "route": {
        "id": 4821,
        "provider": "CH4",
        "prefix": "34",
        "price": 0.045
    }
}
{
    "info": "ROUTE_DELETED",
    "removed": 1
}
{
    "last_updated": "2026-09-03T13:02:57Z",
    "country": "ES",
    "prefix": "34",
    "count": 437,
    "blacklist": [
        { "url": "34600111222", "type": "number" },
        { "url": "34600333444", "type": "number" }
    ]
}
{
    "info": "HLR_JOB_SUBMITTED",
    "job_id": 551,
    "batch_ref": "HLR-7KQ2M9",
    "numbers_count": 2,
    "estimated_cost": 0.02
}
{
    "job_id": 551,
    "status": "Completed",
    "billed_amount": 0.02,
    "refunded_numbers": 1,
    "refunded_amount": 0.01,
    "current_balance": 541.44,
    "created_date": "2026-09-04 03:15:10",
    "results": {
        "success": true,
        "data": [
            {
                "msisdn": "34600111222",
                "mccmnc": "21407",
                "is_ported": false,
                "network_name": "Movistar",
                "country_code": "ES",
                "ported_network": null,
                "query_status": "OK",
                "api_cost": "0.0025",
                "processing_status": "COMPLETED"
            }
        ]
    }
}
{
    "info": "Successfully retrieved available networks.",
    "data": [
        { "mccmnc": "21407", "country": "Spain", "isoAlpha2": "ES", "network": "Movistar" },
        { "mccmnc": "20810", "country": "France", "isoAlpha2": "FR", "network": "SFR" },
        { "mccmnc": "27601", "country": "Albania", "isoAlpha2": "AL", "network": "One Albania", "mccmncOriginal": "27602" }
    ]
}
{
    "info": "Test created successfully.",
    "billed": {
        "amount": "0.70",
        "cost_per_test": "0.35",
        "tests_created": 2,
        "new_balance": "540.76"
    },
    "api_response": {
        "phoneNumbers": [
            { "id": 150917489, "messageId": "61352", "msisdn": "34600111222" },
            { "id": 150917490, "messageId": "61353", "msisdn": "33612345678" }
        ]
    }
}
{
    "info": "Successfully retrieved test result.",
    "data": {
        "testId": 150917489,
        "createdOn": "2026-09-04T03:20:55.000+0000",
        "mccMnc": "21407",
        "mccMncPorted": "21407",
        "deliveredSender": "MyBrand",
        "deliveredText": "61352 Hello, this is my message",
        "phone": "34600111222",
        "messageId": "61352",
        "receiptTime": "2026-09-04T03:21:46.000+0000",
        "receiptStatus": "POSITIVE",
        "pdu": "0891...",
        "scts": "2026-09-04T03:21:41.000+0000",
        "scn": "+34609090909",
        "arrivalTs": "2026-09-04T03:21:45.000+0000"
    }
}
Keep your token private

The token can send messages and spend your balance. Never put it in a web page or a mobile app, and generate a new one from the panel if you think it leaked.

Before you start
One URL for everything

Every call is a POST to https://rest.ankarex.ltd/ with a JSON body. There are no paths to remember: what you want to do goes inside the JSON.

The token goes in the body

Add "token": "…" to the JSON. No headers, no signatures. The only call that works without a token is Get Available Test Networks.

Two kinds of calls

"send" does something (send SMS, run an HLR, pin a route). "status" asks something (balance, blacklist, HLR results). If you put both in one request, send wins.

What costs money

Only Send SMS (per SMS, when dispatched), Submit HLR Job (0.01 EUR per number) and Create SMS Test (0.35 EUR per network). Everything else is free.

Errors look the same

Business errors come as HTTP 400 with {"error":"true","info":"a readable message"}. Check error first, then read info. Full list in Errors and limits.

Limits

500 requests per minute per IP. A bulk send with thousands of numbers is a single request, so this is rarely a concern.

Account
Get Account Status
POST
Free

The simplest call of the API and the best place to start. It tells you how much balance you have, which user the token belongs to, and from where and when the token was last used.

Call it once when you set up your integration to confirm the token works, and whenever you want to check your balance before a large campaign.

Parameters
NameTypeWhat it is
statusrequiredstringAlways "account".
{
    "token": "YOUR-API-TOKEN-XXXX-XXXX-XXXX-XXXX-XXXX",
    "status": "account"
}

Response

{
    "balance": "541.58",
    "info": "CONNECTED_API",
    "username": "MYUSER",
    "user_id": 261,
    "role": "user",
    "is_admin": false,
    "token_activity": {
        "last_ip": "203.0.113.10",
        "last_user_agent": "curl/8.21.0",
        "last_used_at": "2026-09-04 03:02:11",
        "uses": 5739,
        "distinct_ips": 5
    }
}
What you get back
balanceYour balance in EUR. Note that it comes as a string here.
infoAlways CONNECTED_API when the token is valid.
username / user_id / roleWho owns the token. is_admin is true only for administration accounts.
token_activityWhere and when this token was used: last IP, last User-Agent, last date, total number of calls and how many different IPs have used it. If distinct_ips grows unexpectedly, someone else may have your token.
Token Activity by IP NEW
POST
Free

Shows the history of IP addresses that have used your token, with the User-Agent, the number of requests and when each one was first and last seen.

Use it as a security check. If an IP you do not recognise appears in the list, generate a new token from this page: the old one stops working immediately.

Parameters
NameTypeWhat it is
statusrequiredstringAlways "token_ips".
limitoptionalintegerMaximum number of IPs to return. Default 20.
{
    "token": "YOUR-API-TOKEN-XXXX-XXXX-XXXX-XXXX-XXXX",
    "status": "token_ips",
    "limit": 20
}

Response

{
    "info": "TOKEN_IPS",
    "activity": {
        "last_ip": "203.0.113.10",
        "last_user_agent": "curl/8.21.0",
        "last_used_at": "2026-09-04 03:06:38",
        "uses": 5765,
        "distinct_ips": 5
    },
    "ips": [
        {
            "ip": "203.0.113.10",
            "user_agent": "curl/8.21.0",
            "requests": 2,
            "first_seen": "2026-09-03 01:16:06",
            "last_seen": "2026-09-04 03:02:11"
        },
        {
            "ip": "198.51.100.7",
            "user_agent": "Ankarex-Telegram-Bot/1.0",
            "requests": 23,
            "first_seen": "2026-09-03 04:35:55",
            "last_seen": "2026-09-04 03:01:08"
        }
    ]
}
What you get back
activityThe same summary you get in Get Account Status.
ips[]One entry per IP with its User-Agent, request count and first/last date. The per-IP counter is updated at most once per hour, so it can be lower than activity.uses.
Telegram bot

If you use the Ankarex Telegram bot with this token, its requests appear here with the User-Agent Ankarex-Telegram-Bot/1.0. That is expected.

Create a Top-up Link NEW
POST
Free

Generates a link to add balance to your account without logging into the panel. The link is valid for 30 minutes.

If you send an amount, the payment is created right away and you get the checkout URL. If you do not, you get a page where the amount is chosen manually. No money moves when you call this endpoint: the balance is credited only after the payment is confirmed.

Parameters
NameTypeWhat it is
sendrequiredstringAlways "topup".
amountoptionalnumberAmount in EUR. Must be greater than 0. There is no maximum; the minimum is set by the payment provider.
{
    "token": "YOUR-API-TOKEN-XXXX-XXXX-XXXX-XXXX-XXXX",
    "send": "topup",
    "amount": 50
}

Response with amount (direct checkout)

{
    "info": "TOPUP_LINK_CREATED",
    "url": "https://.../checkout/...",
    "direct": true,
    "expires_at": 1756955000,
    "expires_in": 1800,
    "balance": 541.46,
    "payment_id": "...",
    "amount": 50,
    "currency": "EUR"
}

Response without amount (top-up page)

{
    "info": "TOPUP_LINK_CREATED",
    "url": "https://app.ankarex.io/topup?u=261&e=1756955000&s=...",
    "direct": false,
    "expires_at": 1756955000,
    "expires_in": 1800,
    "balance": 541.46
}
What you get back
urlOpen it in a browser to pay.
directtrue when the payment was created and url is the checkout itself. If the payment provider does not answer, it falls back to false and returns the top-up page instead.
expires_at / expires_inWhen the link stops working (Unix timestamp) and how many seconds it is valid (1800).
payment_id, amount, currencyOnly present when direct is true.
Errors you may get
infoWhy
Required parameters are missing.amount is zero, negative or not a number.

All errors are HTTP 400 with {"error":"true","info":"…"} unless stated otherwise. See Errors and limits.

Sending
Send SMS
POST
Per SMS, charged when each message is dispatched

Sends one or many SMS in a single request. You get an answer immediately with a batch_id; the messages are then dispatched in the background and you follow them with the Delivery Report endpoint.

How billing works: you are not charged when you call this endpoint. Each message is charged when it is actually dispatched, at the price of its destination country multiplied by the number of SMS parts. The estimated_cost in the response tells you what that will be. If your balance is lower than the estimate, nothing is queued and you get Insufficient balance.

Parameters
NameTypeWhat it is
sendrequiredstringAlways "bulk".
torequiredstringOne or more numbers in international format without the +, separated by commas. Spaces and any other characters are removed automatically.
sender_idrequiredstringThe name or number the recipient sees as sender. Maximum 11 characters (longer values are cut). See the Sender ID rules below.
message_contentrequiredstringThe text of the message, UTF-8. Real line breaks are allowed. Longer texts are cut to the limit (see below).
unicodeoptionalbooleantrue allows up to 134 Unicode characters split into 2 parts (double price). Default false.
optionoptionalstringAn option_id from Get Route Options to force a specific channel. Numbers outside that option's country are rejected as no_route.
normalize_urlsoptionalbooleanRemoves the trailing slash from URLs such as https://example.com/.
{
    "token": "YOUR-API-TOKEN-XXXX-XXXX-XXXX-XXXX-XXXX",
    "send": "bulk",
    "to": "34600111222,33612345678",
    "sender_id": "MyBrand",
    "message_content": "Hi #RND, your code is 1234.\nThank you.",
    "unicode": false
}

Response — queued

{
    "info": "QUEUED",
    "batch_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "campaigns_count": 2,
    "numbers_count": 2,
    "estimated_cost": 0.1,
    "balance": 541.46,
    "rejected": {
        "invalid_format": 0,
        "no_route": 0
    }
}

Response — more than 500 recipients

{
    "info": "QUEUED",
    "batch_id": "6d0b2a1c-9e3f-4b77-a1d2-0f1e2d3c4b5a",
    "campaigns_count": 1200,
    "numbers_count": 1200,
    "estimated_cost": 60.0,
    "balance": 541.46,
    "rejected": { "invalid_format": 3, "no_route": 1 },
    "batch_ids": [
        "6d0b2a1c-9e3f-4b77-a1d2-0f1e2d3c4b5a",
        "b7c8d9e0-1122-4334-9556-677889900aab",
        "c1d2e3f4-5566-4778-8990-a1b2c3d4e5f6"
    ],
    "batch_count": 3
}

Response — identical request repeated within 2 minutes

{
    "info": "QUEUED",
    "duplicate": true,
    "batch_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "note": "Identical submission already accepted; returning existing batch_id."
}

Error 400 — no valid recipients

{
    "error": "true",
    "info": "NO_VALID_NUMBERS",
    "rejected": {
        "invalid_format": 2,
        "no_route": 0
    }
}
What you get back
batch_idThe identifier of this batch. Keep it: it is what you pass to the Delivery Report.
campaigns_count / numbers_countHow many messages were created (one per valid recipient).
estimated_costWhat the batch will cost once dispatched, in EUR.
balanceYour balance before the charge.
rejectedHow many numbers were dropped: invalid_format (bad number) and no_route (no coverage for that country, or the number did not match the option you forced).
batch_ids / batch_countOnly when you send more than 500 numbers: the batch is split into blocks of 500, each with its own id. Pass them all, comma-separated, to the Delivery Report.
unicode_warningOnly when the text contains characters outside the standard SMS alphabet or was cut. It lists the characters that triggered Unicode and how long the text was.
Message length and Unicode

The API detects the alphabet of your text automatically:

  • Standard alphabet (GSM-7): up to 160 characters in one SMS. Includes letters, digits, basic punctuation and characters such as Ä ä Å å Ö ö Ü ü É é è à Ñ ñ Ø ø Æ æ Ç ¿ ¡ ß ù ì ò £ ¥ €. The characters | ^ { } [ ] ~ \ count as two.
  • Unicode (UCS-2): triggered by emojis, Cyrillic, Chinese, Arabic and also by ç (lowercase), È, À and the backtick. Limit 70 characters in one SMS (an emoji counts as two).
  • With unicode: true a Unicode text can reach 134 characters, sent as 2 parts and billed twice.

Text above the limit is cut, not rejected. The response tells you in unicode_warning.

Sender ID rules

Some destinations change the sender automatically. These rules are checked in this order, looking at all recipients of the request:

  1. Any number starting with 353 (Ireland): the sender becomes 50300 for the whole batch.
  2. Any number starting with 7: the sender becomes a random 11-letter name.
  3. If sender_id is the word sms: a random 4-5 character name is used.
  4. Otherwise your sender_id is used, cut to 11 characters.

Countries marked LLC or SHORTCODE in the price list may replace the sender with an operator number. Use the Test Tool to see what actually arrives.

#RND: a different code for every recipient

Write #RND anywhere in the text and each recipient receives 4 different random letters/digits in its place. Useful to avoid identical-content filters and to make each message unique.

Safe retries (no double sending)

If your connection drops and you do not know whether the request arrived, just send it again with exactly the same content within 2 minutes. You will get duplicate: true and the original batch_id: nothing is sent or charged twice. If the first request is still being processed you get an HTTP 409 asking you to wait a few seconds. If you really want to send the same text to the same numbers again, change something (for example add #RND) or wait 2 minutes.

Numbers that are always filtered

Numbers in the opt-out blacklist are silently dropped. Numbers with an invalid format (fewer than 7 or more than 15 digits, leading zero, repeated or sequential digits such as 1111111 or 1234567) are rejected. Download the blacklist and clean your list before sending to keep rejected at zero.

Errors you may get
infoWhy
Required parameters are missing.to, sender_id or message_content is missing.
SID is Required.sender_id is empty.
Invalid phone numbers provided.to contains no digits at all.
NO_VALID_NUMBERSEvery number was rejected. The response includes the rejected counters.
ALL_NUMBERS_BLACKLISTEDEvery valid number is in the opt-out list. The response includes rejected.blacklisted.
Invalid or non-existent option.The option does not exist.
That route is temporarily unavailable…The channel behind option is switched off. Send without option to use the default route.
Insufficient balance.Your balance is lower than estimated_cost. Nothing was queued.
DUPLICATE_IN_PROGRESS (HTTP 409)The same request is still being processed. Wait a few seconds.

All errors are HTTP 400 with {"error":"true","info":"…"} unless stated otherwise. See Errors and limits.

Get Delivery Report (DLR)
POST
Free

Tells you what happened to the messages you sent: delivered, failed, expired or still on the way. You can ask for a whole batch (recommended) or for a single message.

For large batches use summary: true: you get the counters without the list of every number, which is much faster. When summary.pending reaches 0 there is nothing left to wait for, and you can request the full detail once if you need it.

Parameters
NameTypeWhat it is
sendrequiredstringAlways "dlr".
batch_idone ofstringOne or more batch_id separated by commas (up to 200 batches, i.e. 100,000 messages). This is what Send SMS returns.
idone ofintegerThe id of a single message (messages[].id). If both are sent, batch_id wins.
summaryoptionalbooleanOnly with batch_id. true returns counters only. Recommended for anything above a few hundred messages.
{
    "token": "YOUR-API-TOKEN-XXXX-XXXX-XXXX-XXXX-XXXX",
    "send": "dlr",
    "batch_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "summary": false
}

Response — batch, full detail

{
    "status": 1,
    "batch_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "total_messages": 2,
    "summary": {
        "delivered": 1,
        "failed": 0,
        "sending": 1,
        "expired": 0,
        "delivery_rate": 50
    },
    "messages": [
        {
            "id": 1759619,
            "msisdn": "34600111222",
            "sender_id": "MyBrand",
            "status": "sent",
            "dlr_status": "DELIVERED",
            "dlr_received_at": "2026-09-04 03:10:12",
            "country": "Spain",
            "country_code": "ES",
            "mcc": "214",
            "submitted_at": "2026-09-04 03:09:58"
        },
        {
            "id": 1759620,
            "msisdn": "33612345678",
            "sender_id": "MyBrand",
            "status": "queued",
            "dlr_status": "PENDING",
            "dlr_received_at": null,
            "country": "France",
            "country_code": "FR",
            "mcc": "208",
            "submitted_at": "2026-09-04 03:09:58"
        }
    ],
    "success": 1,
    "fail": 0,
    "deliverSuc": 1,
    "deliverFail": 0,
    "deliverTimeout": 0
}

Response — batch with summary: true

{
    "status": 1,
    "batch_id": "uuid1,uuid2",
    "batches": 2,
    "total_messages": 1000,
    "summary": {
        "delivered": 930,
        "failed": 40,
        "sending": 30,
        "expired": 0,
        "suspended": 10,
        "pending": 20,
        "delivery_rate": 93
    },
    "messages": [],
    "summary_only": true,
    "success": 930,
    "fail": 40,
    "deliverSuc": 930,
    "deliverFail": 40,
    "deliverTimeout": 0
}

Response — single message by id

{
    "status": 1,
    "campaign_id": 1759619,
    "batch_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "campaign_status": "sent",
    "dlr_status": "DELIVERED",
    "dlr_received_at": "2026-09-04 03:10:12",
    "error_code": "000",
    "smpp_message_id": "5f3a9c1e",
    "array": [[1759619, "5f3a9c1e", "2026-09-04 03:09:58", "2026-09-04 03:10:12", "000", 3]],
    "success": 1,
    "fail": 0,
    "unsent": 0,
    "sending": 0,
    "nofound": 0,
    "deliverSuc": 1,
    "deliverFail": 0,
    "deliverTimeout": 0
}
What you get back
summary.delivered / failed / expiredMessages confirmed by the operator as delivered, not deliverable, or without confirmation after 24 hours.
summary.sendingMessages still without a final confirmation.
summary.suspended(only with summary: true) Messages that could not be charged because your balance ran out. They will not move on their own: add balance and contact support.
summary.pending(only with summary: true) sending minus suspended: what is genuinely still in progress.
summary.delivery_rateDelivered ÷ total, in percent.
messages[]One entry per recipient with its number, sender, current status, delivery status and dates. Empty when summary: true.
success, fail, deliverSuc, deliverFail, deliverTimeout, arrayOlder fields kept so existing integrations keep working. New integrations should read summary and messages.
Reading the statuses

status is the stage of the message on our side: wait (accepted), queued (charged, in the queue), sent (handed to the operator), suspended (could not be charged), rejected or failed.

dlr_status is what the operator reported: PENDING / PROCESSING / SUBMITTED (no final answer yet), DELIVERED, FAILED, REJECTED, EXPIRED (no answer after 24 hours; it can still change if a late confirmation arrives).

In the single-message answer, the last value of array[0] is a numeric code: 1 sending, 2 failed, 3 delivered, 4 expired.

Reports are kept for at least 30 days

You do not need to store the delivery data on your side right away; you can query it later.

Errors you may get
infoWhy
Missing or invalid id or batch_id parameter.Neither id nor batch_id was sent.
This job does not belong to you.The id does not exist or belongs to another account.
Batch not found or does not belong to you.None of the batch_id values exist for your account.

All errors are HTTP 400 with {"error":"true","info":"…"} unless stated otherwise. See Errors and limits.

Routes & prices
Get Coverage & Prices
POST
Free

The list of countries you can send to, with the price per SMS and the type of route. If you have a negotiated price for a country, it appears here as special_rate_price.

Prices can change, so read this list from your integration instead of hard-coding them.

Parameters
NameTypeWhat it is
sendrequiredstringAlways "prices".
{
    "token": "YOUR-API-TOKEN-XXXX-XXXX-XXXX-XXXX-XXXX",
    "send": "prices"
}

Response (excerpt)

{
    "data": [
        {
            "name": "Spain",
            "prefix": "34",
            "price": 0.05,
            "type": "OPEN SID",
            "special_rate": false,
            "special_rate_price": null
        },
        {
            "name": "Belgium",
            "prefix": "32",
            "price": 0.075,
            "type": "SHORTCODE",
            "special_rate": false,
            "special_rate_price": null
        },
        {
            "name": "Mexico",
            "prefix": "52",
            "price": 0.014,
            "type": "LLC",
            "special_rate": true,
            "special_rate_price": 0.012
        }
    ]
}
What you get back
name / prefixCountry and international dialling prefix.
pricePrice per SMS part in EUR.
typeOPEN SID: your alphanumeric sender is delivered as is. LLC: the operator may replace the sender with a long number. SHORTCODE: the sender is replaced by a short number.
special_rate / special_rate_pricetrue and your negotiated price when you have one for that country; otherwise false and null.
Get Route Options
POST
Free

Every country is served by one or more channels (CH1, CH4, CH6…). This endpoint lists all the country × channel combinations you can choose from, grouped by country.

Each option has an option_id. Use it in Send SMS (parameter option) to force a channel for one request, or in Set Route to make it permanent for that country. Only channels that are currently switched on are listed.

Parameters
NameTypeWhat it is
sendrequiredstringAlways "getoptions".
{
    "token": "YOUR-API-TOKEN-XXXX-XXXX-XXXX-XXXX-XXXX",
    "send": "getoptions"
}

Response (excerpt)

{
    "options": {
        "AUSTRIA": [
            {
                "option_id": "cbc1501ae5ace16f333d7fd0e84754da",
                "name": "CH1",
                "provider": "CH1",
                "country": "Austria",
                "country_code": "AT",
                "price": 0.045,
                "prefix": "43",
                "type": "OPEN SID",
                "implementation": "53"
            },
            {
                "option_id": "5d7d083aa9fbdf46fb90363937f8a8f3",
                "name": "CH4",
                "provider": "CH4",
                "country": "Austria",
                "country_code": "AT",
                "price": 0.045,
                "prefix": "43",
                "type": "OPEN SID",
                "implementation": "22"
            }
        ],
        "SPAIN": [
            {
                "option_id": "9a1f0c2e7d3b4a5c6e7f8091a2b3c4d5",
                "name": "CH4",
                "provider": "CH4",
                "country": "Spain",
                "country_code": "ES",
                "price": 0.05,
                "prefix": "34",
                "is_custom": true
            }
        ]
    }
}
What you get back
option_idThe identifier you pass to Send SMS or Set Route.
name / providerThe channel (CH1, CH4, CH6, CH6-1, CH7, CH9…). Both fields carry the same value; name is kept for older integrations.
country / country_code / prefixThe destination the option applies to.
pricePrice per SMS through that channel.
type / implementationRoute type and channel identifier. Present on standard options only.
is_customtrue on the options you have already pinned with Set Route.
The order is stable

Options come in the same order every time, so you can safely show them in a list.

My Pinned Routes NEW
POST
Free

Lists the routes you have pinned with Set Route (or that support pinned for you). A pinned route is used for every message you send to that country until you remove it.

Parameters
NameTypeWhat it is
statusrequiredstringAlways "my_routes".
{
    "token": "YOUR-API-TOKEN-XXXX-XXXX-XXXX-XXXX-XXXX",
    "status": "my_routes"
}

Response

{
    "info": "MY_ROUTES",
    "routes": [
        {
            "id": 4821,
            "provider": "CH4",
            "channel": "CH4",
            "country": "Spain",
            "prefix": "34",
            "price": 0.05,
            "by_admin": false
        }
    ]
}
What you get back
idThe identifier to pass to Delete Route.
provider / channelThe channel the country is pinned to.
priceThe price you pay through that channel.
by_admintrue when the price was set by support as a negotiated rate. You can change the channel but not the price, and you cannot delete it.
Set Route (pin a channel) NEW
POST
Free

Pins a channel for a country. From then on, every message to that country goes through that channel until you delete the route. Only one route per country: pinning a different channel replaces the previous one.

This is the same as the Route Switcher in the panel, available from your code.

Parameters
NameTypeWhat it is
sendrequiredstringAlways "set_route".
optionrequiredstringThe option_id from Get Route Options.
{
    "token": "YOUR-API-TOKEN-XXXX-XXXX-XXXX-XXXX-XXXX",
    "send": "set_route",
    "option": "5d7d083aa9fbdf46fb90363937f8a8f3"
}

Response

{
    "info": "ROUTE_SET",
    "operation": "created",
    "route": {
        "id": 4821,
        "provider": "CH4",
        "prefix": "34",
        "price": 0.045
    }
}
What you get back
operationcreated if the country had no pinned route, updated if it replaced an existing one.
routeWhat was pinned: id, channel, prefix and price.
Negotiated prices are kept

If support gave you a special price for that country, pinning another channel changes the channel but keeps your price.

Errors you may get
infoWhy
Required parameters are missing.option is missing.
Invalid or non-existent option.The option_id does not exist or its channel is switched off.

All errors are HTTP 400 with {"error":"true","info":"…"} unless stated otherwise. See Errors and limits.

Delete Route (unpin) NEW
POST
Free

Removes a pinned route. Messages to that country go back to the default channel.

Parameters
NameTypeWhat it is
sendrequiredstringAlways "delete_route".
idone ofintegerThe route id from My Pinned Routes. Wins over prefix if both are sent.
prefixone ofstringThe country prefix, e.g. "34".
{
    "token": "YOUR-API-TOKEN-XXXX-XXXX-XXXX-XXXX-XXXX",
    "send": "delete_route",
    "prefix": "34"
}

Response

{
    "info": "ROUTE_DELETED",
    "removed": 1
}
What you get back
removedHow many routes were removed.
Errors you may get
infoWhy
Required parameters are missing.Neither id nor prefix was sent.
Invalid or non-existent option.There is no pinned route with that id or prefix.
This route was set by an administrator and cannot be removed.The route carries a negotiated price set by support. Contact support to change it.

All errors are HTTP 400 with {"error":"true","info":"…"} unless stated otherwise. See Errors and limits.

Blacklist
Get Blacklist
POST
Free

The global opt-out list: numbers that asked not to receive messages, plus blocked content patterns. Messages to these numbers are never sent, so use this list to clean your contacts before you send.

The list is served from a cache and is safe to request often. Save last_updated and download it again only when that value changes.

Parameters
NameTypeWhat it is
statusrequiredstringAlways "blacklist".
countryoptionalstringRestrict the list to one country: an ISO code such as "ES" or a prefix such as "34". Strongly recommended, the full list is over 100,000 entries.
{
    "token": "YOUR-API-TOKEN-XXXX-XXXX-XXXX-XXXX-XXXX",
    "status": "blacklist",
    "country": "ES"
}

Response — filtered by country

{
    "last_updated": "2026-09-03T13:02:57Z",
    "country": "ES",
    "prefix": "34",
    "count": 437,
    "blacklist": [
        { "url": "34600111222", "type": "number" },
        { "url": "34600333444", "type": "number" }
    ]
}

Response — full list (no country)

{
    "last_updated": "2026-09-03T13:02:57Z",
    "count": 102967,
    "blacklist": [
        { "url": "12025550101", "type": "number" },
        { "url": "12025550102", "type": "number" },
        { "url": "bit.ly", "type": "content" }
    ]
}
What you get back
last_updatedUTC date of the last change. Compare it with your saved copy before downloading again.
countNumber of entries.
blacklist[].urlThe phone number (international format, no +) or the blocked content/URL pattern.
blacklist[].typenumber or content. With country only numbers are returned.
Compression

The full list is several megabytes. Send Accept-Encoding: gzip and the API compresses it for you. The full-list response comes without indentation.

Errors you may get
infoWhy
Invalid country filter…country is not a valid ISO code or prefix.

All errors are HTTP 400 with {"error":"true","info":"…"} unless stated otherwise. See Errors and limits.

HLR
Submit HLR Job
POST
0.01 EUR per number, charged on submission; unresolved numbers are refunded

HLR lookup tells you whether a number is active, which operator serves it and whether it has been ported. Send a list of numbers; the job runs in the background and you collect the results with Get HLR Job Status.

How billing works: 0.01 EUR per unique valid number is taken from your balance when you submit. If the operator cannot resolve a number, that number is refunded automatically when the job finishes.

Parameters
NameTypeWhat it is
sendrequiredstringAlways "hlr".
torequiredstringNumbers separated by commas, international format, with or without +. Invalid numbers are ignored as long as at least one is valid. Duplicates are removed and not charged twice.
{
    "token": "YOUR-API-TOKEN-XXXX-XXXX-XXXX-XXXX-XXXX",
    "send": "hlr",
    "to": "+34600111222, 33612345678"
}

Response

{
    "info": "HLR_JOB_SUBMITTED",
    "job_id": 551,
    "batch_ref": "HLR-7KQ2M9",
    "numbers_count": 2,
    "estimated_cost": 0.02
}
What you get back
job_idUse it in Get HLR Job Status.
batch_refA short human-friendly reference, the same you see in the panel.
numbers_countUnique valid numbers accepted.
estimated_costWhat was charged: numbers_count × 0.01.
Errors you may get
infoWhy
Required parameters are missing.to is missing.
Invalid phone numbers provided.No valid number in to.
Insufficient balance.Not enough balance for numbers_count × 0.01.

All errors are HTTP 400 with {"error":"true","info":"…"} unless stated otherwise. See Errors and limits.

Get HLR Job Status & Results
POST
Free

Returns the state of an HLR job, what was billed and refunded, and the result for every number resolved so far. While the job is In Progress the list grows; poll every 10–30 seconds until the status is Completed.

Parameters
NameTypeWhat it is
statusrequiredstringAlways "hlr".
idrequiredintegerThe job_id you received on submission.
{
    "token": "YOUR-API-TOKEN-XXXX-XXXX-XXXX-XXXX-XXXX",
    "status": "hlr",
    "id": 551
}

Response

{
    "job_id": 551,
    "status": "Completed",
    "billed_amount": 0.02,
    "refunded_numbers": 1,
    "refunded_amount": 0.01,
    "current_balance": 541.44,
    "created_date": "2026-09-04 03:15:10",
    "results": {
        "success": true,
        "data": [
            {
                "msisdn": "34600111222",
                "mccmnc": "21407",
                "is_ported": false,
                "network_name": "Movistar",
                "country_code": "ES",
                "ported_network": null,
                "query_status": "OK",
                "api_cost": "0.0025",
                "processing_status": "COMPLETED"
            }
        ]
    }
}
What you get back
statusPending, In Progress, Completed, Failed (Insufficient Funds) or Failed (No Results - Refundable).
billed_amountWhat was charged on submission.
refunded_numbers / refunded_amountNumbers the operator could not resolve and the amount returned to your balance for them.
results.data[]One entry per resolved number: operator code (mccmnc), operator name, country, whether it is ported and to which network. Numbers that could not be resolved are not listed (they are refunded).
Errors you may get
infoWhy
Job ID is missing or invalid.id is missing.
This job does not belong to you.The job does not exist or belongs to another account.

All errors are HTTP 400 with {"error":"true","info":"…"} unless stated otherwise. See Errors and limits.

Test Tool
Get Available Test Networks
POST
Free, no token needed

The Test Tool lets you send a message to a real phone on a real network and see exactly how it arrives: which sender was shown, the exact text, and on which network. This endpoint lists the networks you can test on.

It is the only call that works without a token.

Parameters
NameTypeWhat it is
sendrequiredstringAlways "get_available_networks".
{
    "send": "get_available_networks"
}

Response (excerpt)

{
    "info": "Successfully retrieved available networks.",
    "data": [
        { "mccmnc": "21407", "country": "Spain", "isoAlpha2": "ES", "network": "Movistar" },
        { "mccmnc": "20810", "country": "France", "isoAlpha2": "FR", "network": "SFR" },
        { "mccmnc": "27601", "country": "Albania", "isoAlpha2": "AL", "network": "One Albania", "mccmncOriginal": "27602" }
    ]
}
What you get back
mccmncThe network code you pass to Create SMS Test.
country / isoAlpha2 / networkWhere and which operator.
mccmncOriginalPresent when a network was merged or renamed: the operator's original code.
Create SMS Test
POST
0.35 EUR per test (per network), charged on creation

Creates a test on each network you choose. For every network you get a real phone number and a short code. Send your SMS to that number with the code inside the text (by your own means or with Send SMS), then read the result with Get SMS Test Result.

How billing works: 0.35 EUR per network is charged when the test is created. If the test cannot be created, the amount is refunded automatically.

Parameters
NameTypeWhat it is
sendrequiredstringAlways "create_test".
networksrequiredarrayA list of objects, each with at least mccmnc, e.g. [{"mccmnc": "21407"}].
callbackUrloptionalstringA URL on your side that receives the result (HTTP POST) as soon as the message reaches the test phone.
{
    "token": "YOUR-API-TOKEN-XXXX-XXXX-XXXX-XXXX-XXXX",
    "send": "create_test",
    "networks": [
        { "mccmnc": "21407" },
        { "mccmnc": "20810" }
    ],
    "callbackUrl": "https://your-server.com/testsms-callback"
}

Response

{
    "info": "Test created successfully.",
    "billed": {
        "amount": "0.70",
        "cost_per_test": "0.35",
        "tests_created": 2,
        "new_balance": "540.76"
    },
    "api_response": {
        "phoneNumbers": [
            { "id": 150917489, "messageId": "61352", "msisdn": "34600111222" },
            { "id": 150917490, "messageId": "61353", "msisdn": "33612345678" }
        ]
    }
}
What you get back
billedWhat was charged and your new balance (as strings with two decimals).
phoneNumbers[].idThe test id for Get SMS Test Result.
phoneNumbers[].messageIdPut this code in the text of the SMS you send, e.g. 61352 Hello, this is my message. Without it the result cannot be matched to your test.
phoneNumbers[].msisdnThe test phone number to send to.
Do not forget the code

The test phone receives many messages. The messageId inside your text is the only way to know which one is yours.

Errors you may get
infoWhy
Networks parameter must be a non-empty array.networks is empty or not a list.
Insufficient balance.Not enough balance for 0.35 × networks.
External API error occurred. Please try again.The test provider did not answer. Nothing was charged.

All errors are HTTP 400 with {"error":"true","info":"…"} unless stated otherwise. See Errors and limits.

Get SMS Test Result
POST
Free

Shows what the test phone received: the sender as displayed, the exact text, the network (and the real network if the number is ported) and the timestamps. Poll it after sending your SMS; delivery to the test phone usually takes seconds to a few minutes.

You can only read tests created with your own account.

Parameters
NameTypeWhat it is
statusrequiredstringAlways "get_sms_test_result".
idrequiredintegerThe test id from Create SMS Test.
{
    "token": "YOUR-API-TOKEN-XXXX-XXXX-XXXX-XXXX-XXXX",
    "status": "get_sms_test_result",
    "id": 150917489
}

Response

{
    "info": "Successfully retrieved test result.",
    "data": {
        "testId": 150917489,
        "createdOn": "2026-09-04T03:20:55.000+0000",
        "mccMnc": "21407",
        "mccMncPorted": "21407",
        "deliveredSender": "MyBrand",
        "deliveredText": "61352 Hello, this is my message",
        "phone": "34600111222",
        "messageId": "61352",
        "receiptTime": "2026-09-04T03:21:46.000+0000",
        "receiptStatus": "POSITIVE",
        "pdu": "0891...",
        "scts": "2026-09-04T03:21:41.000+0000",
        "scn": "+34609090909",
        "arrivalTs": "2026-09-04T03:21:45.000+0000"
    }
}
What you get back
receiptStatusPOSITIVE means the message arrived on the test phone. Other values mean not received yet or not received.
deliveredSenderThe sender exactly as the phone displayed it. This is how you check whether your Sender ID was kept or replaced.
deliveredTextThe text exactly as received.
mccMnc / mccMncPortedTarget network and, if the number is ported, the real one.
receiptTime / arrivalTs / sctsWhen the message arrived.
pdu / scnTechnical detail: the raw PDU and the message-centre number.
Errors you may get
infoWhy
Job ID is missing or invalid.id is missing.
This test does not belong to you.The test does not exist or was created by another account.

All errors are HTTP 400 with {"error":"true","info":"…"} unless stated otherwise. See Errors and limits.

Reference
Errors and limits

Every error of the API has the same shape, so one piece of code handles them all:

{
    "error": "true",
    "info": "Authentication token is missing."
}

Note that error is the string "true", not a boolean.

Errors that can happen on any call
infoWhy
Invalid JSON format.The body is not valid JSON.
Authentication token is missing.No token in the body (also for GET requests or empty bodies).
Invalid token - Get a new token in the app.The token does not exist. Generate a new one on this page.
API token has expired - Generate a new token in the app.The token has an expiry date that has passed.
This account is suspended. Contact support.The account is suspended.
The requested operation is invalid.Neither send nor status in the body.
UNKNOWN_ERRORSomething unexpected happened on our side. Try again; if it persists, contact support with the time of the call.
HTTP codes other than 200 and 400
CodeMeaning
409DUPLICATE_IN_PROGRESS — the same Send SMS request is still being processed. Wait a few seconds and retry.
429Rate limit: more than 500 requests per minute from your IP. The answer includes retry_after (seconds) and a Retry-After header. A burst of more than 100 requests in 5 seconds blocks the IP for 30 minutes.
403Access denied (empty body).
200 with empty bodyYou sent a send or status value that does not exist. Check the spelling.
Message status (status)
waitAccepted, waiting to be charged and dispatched.
queuedCharged and in the queue to the operator.
sentHanded to the operator, waiting for the delivery confirmation.
suspendedCould not be charged (balance ran out). It will not move on its own.
rejectedRejected: no coverage or channel switched off.
failedSending failed.
Delivery status (dlr_status)
PENDING / PROCESSING / SUBMITTEDNo final confirmation yet.
DELIVEREDDelivered to the handset.
FAILEDNot deliverable (unknown number, handset off for too long, blocked, DND…).
REJECTEDRejected by the operator.
EXPIREDNo confirmation after 24 hours. It can still become DELIVERED or FAILED if a late confirmation arrives.
Good habits
  • Retry safely. If a Send SMS call times out, repeat it unchanged within 2 minutes: you get the original batch_id back, nothing is sent twice.
  • Poll delivery with summary: true every 30–60 seconds until pending is 0, then fetch the detail once.
  • Clean your lists with the blacklist before sending, and re-download it only when last_updated changes.
  • Read prices from the API instead of hard-coding them.
  • Check the body is not empty before parsing JSON: a misspelled operation returns 200 with no body.
  • Keep the token out of browsers and apps: it can spend your balance. Check Token Activity by IP now and then.

Frequently Asked Questions

Do I need anything besides the token?
No. One token gives access to everything: sending, delivery reports, prices, blacklist, HLR and the Test Tool. Put it in the token field of the JSON body. There are no headers to sign and no IP whitelist.
Which calls cost money?
Only three: Send SMS (per SMS, charged when each message is dispatched), Submit HLR Job (0.01 EUR per number, unresolved numbers refunded) and Create SMS Test (0.35 EUR per network). Everything else is free, including delivery reports and the blacklist.
What is the maximum message length?
160 characters with the standard alphabet. If the text contains emojis or characters outside that alphabet (including lowercase ç), the limit is 70 characters, or 134 with unicode: true (sent as 2 parts, billed twice). Longer texts are cut, not rejected, and the response tells you in unicode_warning.
How do I send to many recipients?
Put all the numbers in to separated by commas, international format without +. A single request can carry tens of thousands of numbers. Above 500 recipients the batch is split into blocks of 500, each with its own batch_id; the response lists them all in batch_ids.
How do I know whether my messages were delivered?
Call Get Delivery Report with the batch_id (or several, comma-separated) and summary: true. Repeat every 30–60 seconds until summary.pending is 0. Then, if you need the detail per number, call it once more without summary.
My request timed out. Did the messages go out?
Send the same request again, unchanged, within 2 minutes. If the first one had been accepted you get duplicate: true with its batch_id and nothing is sent twice. If it had not, it is processed normally.
Why were some numbers rejected?
rejected.invalid_format counts numbers that are not valid phone numbers (too short, leading zero, repeated or sequential digits). rejected.no_route counts numbers in countries we do not cover, or that did not match the option you forced. Numbers in the opt-out blacklist are dropped silently: download the blacklist and filter your list first.
Can I choose which channel my messages use?
Yes. Get Route Options lists the channels per country with an option_id. Pass it as option in Send SMS for one request, or use Set Route to pin it for every future message to that country. My Pinned Routes shows what is pinned and Delete Route removes it.
What are the Sender ID rules?
Up to 11 characters. In countries marked OPEN SID it is delivered as you send it. In LLC and SHORTCODE countries the operator may replace it with a number. Ireland always uses 50300, numbers starting with 7 get a random alphabetic sender, and the word sms as sender produces a random short name.
Is there a rate limit?
500 requests per minute per IP address, and no more than 100 requests in any 5-second window. A bulk request with 40,000 numbers counts as one request, so the limit is rarely a problem. If you hit it you get HTTP 429 with retry_after.
How do I know if someone else is using my token?
Get Account Status shows how many different IPs have used the token and Token Activity by IP lists them. If you see an IP you do not recognise, generate a new token on this page; the old one stops working at once.