SMS Gateway API
Base URL
https://gateway.tastechgroup.comGetting started
The SMS Gateway is a JSON API. Every request goes to https://gateway.tastechgroup.com, carries an API key, and answers JSON. An account admin creates keys in Admin-IQ, under SMS Gateway → API → Keys, and can try any request there on the Console tab. Not a customer yet? Request access.
Authentication
Put your key in the Authorization header as a bearer token. A key is 64 hexadecimal characters. It identifies your account, so every request is scoped to you automatically. There is no account parameter to pass, and none that would let you read anyone else's data.
curl 'https://gateway.tastechgroup.com/api/v1/messages?limit=5' \
-H 'Authorization: Bearer YOUR_API_KEY' Each key carries one or more scopes, chosen when you create it. A request to an endpoint your key does not have the scope for answers 403.
| Scope | Grants |
|---|---|
| messages:read | Read your message history and fetch a single message. |
| messages:write | Send messages. |
| metrics:read | Read delivery and volume metrics for your account. |
Responses
A successful response is wrapped: { "success": true, "data": … }. On list endpoints, pagination sits beside data rather than inside it. GET /api/v1/health is the one exception and is not wrapped at all.
Every response carries an x-correlation-id header. Send us that value when something looks wrong, because it is how we find your request in our logs. You can set it yourself on the way in and we will echo it back.
Errors
A failure answers a JSON body with an error object:
{
"error": {
"message": "Insufficient scope: requires 'messages:write'",
"code": "INTERNAL_ERROR",
"correlationId": "7c1f0b6e-2a94-4c3d-8f51-0d9e6a2b4c88"
}
}code field currently reports INTERNAL_ERROR for most failures regardless of what went wrong, so it is not a reliable signal. The HTTP status and the message are. For the same reason, a validation failure tells you that something was rejected but not which field, so check the parameter tables below when you get a 400. | Status | Meaning |
|---|---|
| 200 | The request succeeded. There is no 201. A created message also answers 200. |
| 400 | The request was rejected. A parameter is missing, malformed, or contradicts another. |
| 401 | The key is missing, wrong, revoked or expired. All four look the same from outside. |
| 403 | The key is valid but does not carry the scope this endpoint needs. |
| 404 | No such record. A record that belongs to another account also answers 404. |
| 429 | Rate limited. Wait for the window in `x-ratelimit-reset` and retry. |
| 500 | Something failed inside the gateway. Quote the correlation id when you report it. |
| 502 | A carrier rejected or failed the request. |
Rate limits
Every response carries x-ratelimit-limit, x-ratelimit-remaining and x-ratelimit-reset. The last is the number of seconds until the window resets, not a timestamp. Read them rather than guessing; the ceiling is roughly 100 requests a minute but it is applied per source address and per server, so treat it as approximate and back off when remaining gets low.
A request over the limit answers 429. There is no Retry-After header, so use x-ratelimit-reset instead.
What your key can see
A customer key sees your messages, not ours. Four fields are removed before a response leaves the gateway: the carrier's name, the carrier's raw response, and the internal error message and code. In their place you get the sending number's label (providerDisplayName) and, on a message that failed for good, two fields that say why in a stable vocabulary. Everything else about your own traffic is yours and is returned.
One asymmetry worth knowing before it costs you an afternoon: you filter on to and from, but the fields that come back are toNumber and fromNumber.
failureReason is permanent when the gateway has stopped trying, retrying while it is still attempting delivery, and null otherwise. failureCategory is one of:
| invalid_destination | The number is not a valid destination. |
| unreachable_destination | The number could not be reached. It is out of service, or unreachable from this route. |
| blocked | The carrier or the recipient blocked the message. |
| configuration | Something about the sending configuration was rejected. |
| provider_unavailable | The gateway could not hand the message to any carrier. Every configured provider was unavailable or failed. |
| opted_out | The recipient opted out by texting STOP. Nothing is sent to them until they text START. |
| carrier_error | The carrier reported a failure it did not attribute more precisely. |
| unknown | The message failed and no reason was recorded. |
Endpoints
/api/v1/messagesmessages:writeSend a message
Queues a text message for delivery from one of your registered numbers.
Body fields
| Name | Type | Notes |
|---|---|---|
| to* | string | The destination number. E.164, e.g. +15551234567 |
| from | string | Which of your registered numbers to send from. Omit to let the gateway choose. E.164 |
| body* | string | The text of the message. 1–1600 characters |
Request body
{
"to": "+15551234567",
"body": "Your appointment is confirmed for Tuesday at 10am."
}Example
curl -X POST 'https://gateway.tastechgroup.com/api/v1/messages' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{ "to": "+15551234567", "body": "Your appointment is confirmed for Tuesday at 10am." }'Response
{
"success": true,
"data": {
"id": "9f8b2c41-5a3e-4d17-9c2b-7e1a4f6d8b03",
"status": "queued",
"direction": "outbound",
"channel": "rest",
"toNumber": "+15551234567",
"fromNumber": "+15559876543",
"body": "Your appointment is confirmed for Tuesday at 10am.",
"segments": 1,
"segmentsEstimated": true,
"retryCount": 0,
"maxRetries": 3,
"providerMessageId": null,
"providerDisplayName": null,
"correlationId": "7c1f0b6e-2a94-4c3d-8f51-0d9e6a2b4c88",
"failureReason": null,
"failureCategory": null,
"sentAt": null,
"deliveredAt": null,
"createdAt": "2026-09-19T14:03:22.114Z",
"updatedAt": "2026-09-19T14:03:22.114Z"
}
}- Sending is asynchronous. A 200 means the gateway accepted the message and queued it, not that it was delivered. Poll GET /api/v1/messages/:id to watch it reach sent and then delivered.
- The response is 200, not 201.
/api/v1/messagesmessages:readSearch messages
Returns your messages, newest first by default. Every filter is optional and they combine. Results are always scoped to your account. There is no parameter that widens them.
Query parameters
| Name | Type | Notes |
|---|---|---|
| limit | integer | How many messages to return. 1–100 · defaults to |
| offset | integer | How many to skip, for paging. 0 or more · defaults to |
| status | string | Filter by status. Repeat the parameter or comma-separate to pass several. Cannot be combined with the failed shorthand. queued, sending, sent, delivered, failed, received, undelivered |
| failed | boolean | Shorthand for status=failed,undelivered. Cannot be combined with status. |
| direction | string | Filter by direction. inbound, outbound |
| channel | string | How the message entered the gateway. keyword_reply is the gateway's own reply to a STOP, START or HELP text. rest, wctp, email, webhook, keyword_reply |
| dateFrom | string | Only messages created at or after this moment. Must not be later than dateTo. ISO 8601 datetime |
| dateTo | string | Only messages created at or before this moment. ISO 8601 datetime |
| to | string | Exact match on the destination number. Note the response field is toNumber. |
| from | string | Exact match on the sending number. Note the response field is fromNumber. |
| correlationId | string | Exact match on the correlation id, which every response echoes as the x-correlation-id header. |
| sort | string | Which column to sort on. createdAt, status, to, from · defaults to |
| order | string | Sort direction. asc, desc · defaults to |
Example
curl -X GET 'https://gateway.tastechgroup.com/api/v1/messages' \
-H 'Authorization: Bearer YOUR_API_KEY'Response
{
"success": true,
"data": [
{
"id": "9f8b2c41-5a3e-4d17-9c2b-7e1a4f6d8b03",
"status": "delivered",
"direction": "outbound",
"channel": "rest",
"toNumber": "+15551234567",
"fromNumber": "+15559876543",
"body": "Your appointment is confirmed for Tuesday at 10am.",
"segments": 1,
"segmentsEstimated": true,
"retryCount": 0,
"maxRetries": 3,
"providerMessageId": "SM4f21c0a9",
"providerDisplayName": "Primary",
"correlationId": "7c1f0b6e-2a94-4c3d-8f51-0d9e6a2b4c88",
"mediaUrls": null,
"wctpMessageId": null,
"failureReason": null,
"failureCategory": null,
"sentAt": "2026-09-19T14:03:23.402Z",
"deliveredAt": "2026-09-19T14:03:26.802Z",
"createdAt": "2026-09-19T14:03:22.114Z",
"updatedAt": "2026-09-19T14:03:26.802Z"
}
],
"pagination": { "total": 1, "limit": 20, "offset": 0 },
"scope": "client",
"clientId": "3c9a77e2-0b41-4f8d-9a16-2d5e8c4b1f07"
}- The pagination object sits beside data, not inside it.
/api/v1/messages/:idmessages:readFetch one message
Returns a single message by id, including its current status. This is how you follow up on something you sent.
Path parameters
| Name | Type | Notes |
|---|---|---|
| id* | string | The message id returned when it was created. |
Example
curl -X GET 'https://gateway.tastechgroup.com/api/v1/messages/:id' \
-H 'Authorization: Bearer YOUR_API_KEY'Response
{
"success": true,
"data": {
"id": "9f8b2c41-5a3e-4d17-9c2b-7e1a4f6d8b03",
"status": "failed",
"direction": "outbound",
"channel": "rest",
"toNumber": "+15551234567",
"fromNumber": "+15559876543",
"body": "Your appointment is confirmed for Tuesday at 10am.",
"segments": 1,
"segmentsEstimated": true,
"retryCount": 3,
"maxRetries": 3,
"providerMessageId": "SM4f21c0a9",
"providerDisplayName": "Primary",
"correlationId": "7c1f0b6e-2a94-4c3d-8f51-0d9e6a2b4c88",
"failureReason": "permanent",
"failureCategory": "invalid_destination",
"sentAt": "2026-09-19T14:03:23.402Z",
"deliveredAt": null,
"createdAt": "2026-09-19T14:03:22.114Z",
"updatedAt": "2026-09-19T14:03:31.008Z"
}
}- A message belonging to another account answers 404, the same as one that does not exist.
/api/v1/metricsmetrics:readRead delivery metrics
Volume and delivery figures for your account over the last hour and the last 24 hours, broken down by channel and by sending number.
Example
curl -X GET 'https://gateway.tastechgroup.com/api/v1/metrics' \
-H 'Authorization: Bearer YOUR_API_KEY'Response
{
"success": true,
"data": {
"windows": {
"1h": {
"sent": 42,
"delivered": 41,
"failed": 1,
"received": 7,
"deliveryRate": 0.976,
"confirmedDeliveryRate": 0.976,
"byChannel": [{ "channel": "rest", "sent": 42 }]
},
"24h": {
"sent": 1204,
"delivered": 1183,
"failed": 5,
"received": 140,
"deliveryRate": 0.982,
"confirmedDeliveryRate": 0.982,
"byChannel": [{ "channel": "rest", "sent": 1204 }]
}
},
"scope": "client",
"clientId": "3c9a77e2-0b41-4f8d-9a16-2d5e8c4b1f07"
}
}- Rates are null, not zero, when nothing was sent in the window. There is no rate to report, which is a different fact from a rate of zero.
- Prefer confirmedDeliveryRate. deliveryRate under-counts while delivery receipts are still outstanding.
/api/v1/healthno token neededService health
Whether the gateway is up. The only endpoint that needs no token, so it is safe to use from an uptime monitor.
Example
curl -X GET 'https://gateway.tastechgroup.com/api/v1/health'Response
{
"status": "ok",
"version": "1.16.0",
"environment": "production",
"uptime": 864213,
"timestamp": "2026-09-19T14:03:22.114Z"
}- This one is not wrapped in the success/data envelope.