API reference
API reference
tsmartSend is a JSON REST API at https://send.tsmart.ai. This section describes every endpoint; this page
covers what they all share.
Base URL
All endpoints are relative to:
https://send.tsmart.ai GET https://send.tsmart.ai/ answers without a key and returns a short description
of the API with its list of resources; every other endpoint needs an API key.
Authentication
Send your API key as a bearer token in the Authorization header. Keys look like
ts_ followed by an 8-character id, an underscore and a 32-character secret, for example
ts_8Kq2Lm7P_…. A key is shown once, when it is created; only a hash is stored.
curl https://send.tsmart.ai/domains \
-H "Authorization: Bearer $TSMART_API_KEY" Create keys in the company console under tsmartSend → API keys, or with POST /api-keys. See API keys for how to store and rotate them.
Permissions
Every key has one of two permissions:
-
sending_accesscan only send:POST /emailsandPOST /emails/batch. It can be restricted to one domain, in which casefrommust use that domain. -
full_accesscan call every endpoint: sending, reading emails and logs, and managing domains, keys, webhooks, suppressions, and the company's mailboxes and groups.
A full-access key acts with the console rights of the person who created it, checked on every call: it can never do more through the API than that person may do in the console, and it loses those rights when the person does. Sending needs no privilege, but every key works only while its creator still has access to the company: removing an admin revokes the keys they created (see API keys).
| Endpoints | Key permission | Key creator needs (console) |
|---|---|---|
POST /emails, POST /emails/batch | any key | nothing |
Other /emails endpoints, GET /domains, GET /domains/{id},
/api-keys, /webhooks, /suppressions, /logs | full_access | the API controls privilege |
POST /domains, PATCH /domains/{id},
POST /domains/{id}/verify | full_access | the Domains privilege |
DELETE /domains/{id} | full_access | to be the company owner |
/mailboxes, /groups | full_access | the privilege of each change: View, Create, Update or Suspend users, Reset user passwords, View or Manage groups (see Mailboxes); deleting a mailbox or a group needs the company owner |
A sending-access key that calls a full-access endpoint gets 401 restricted_api_key. A full-access key
whose creator lacks the right gets 403 invalid_permission.
Requests and responses
Send request bodies as JSON with Content-Type: application/json. Field names are snake_case. Every
response from the API, including errors, is JSON.
The JSON body of most endpoints may be at most 1 MB (64 KB for /mailboxes and /groups); a
larger one is refused with 413 payload_too_large. The body of POST /emails and POST /emails/batch may be
at most 30 MB, enough for the 20 MB of attachments an email may carry (base64 encoding makes a file about a third
larger).
A body over 30 MB, on any endpoint, is refused before the request reaches the API. That answer is a
413 without a JSON body.
Errors
Errors use the HTTP status code and always have the same body: the status code, a stable machine-readable
name and a human-readable message. Match on name; the message text may change.
{
"statusCode": 422,
"name": "validation_error",
"message": "`to` is required"
} | Status | Name | Meaning |
|---|---|---|
| 400 | invalid_idempotency_key | The Idempotency-Key header is not 1 to 256 characters long. |
| 401 | missing_api_key | No Authorization: Bearer … header. |
| 401 | restricted_api_key | A sending-access key called an endpoint that needs full access. |
| 403 | invalid_api_key | The key is malformed, unknown or revoked, or the company is suspended. |
| 403 | service_disabled | tsmartSend is turned off for the company. |
| 403 | account_suspended | The company has an overdue invoice: the service is suspended until it is paid. The message names the invoice and the payment page; paying restores access at once. |
| 403 | ip_not_allowed | The company allows API calls only from listed addresses, and this one is not listed. |
| 403 | invalid_permission | The person who created the key may not do this in the console. |
| 403 | invalid_from_address | The from domain is not one of the company's domains with proven ownership, or the key is restricted to another domain. |
| 403 | domain_not_verified | The from domain's SPF and DKIM records were not found at its last check. Publish them and call POST /domains/{id}/verify. |
| 403 | account_inactive | The company's subscription is not active. |
| 404 | not_found | Unknown path, or the object does not exist in your company. |
| 405 | method_not_allowed | The path exists but not with this HTTP method. |
| 409 | invalid_idempotent_request | The idempotency key was used in the last 24 hours with a different body. |
| 409 | concurrent_idempotent_requests | A request with the same idempotency key is still running. |
| 409 | resource_in_use | The domain still has mailboxes and cannot be deleted. |
| 409 | already_exists | The address of a new mailbox, alternate email or group is already used by a mailbox, a group or an alternate email. |
| 413 | payload_too_large | The request body is over the endpoint's 1 MB limit (see Requests and responses). |
| 422 | missing_required_field | A required field is missing. |
422 (403 or 429 in some cases on POST /domains, POST /webhooks and POST /mailboxes) | validation_error | A field, query parameter or header has an invalid value, or the body is not valid JSON. The message says which. POST /domains answers 403 for a domain that cannot be added and 429 when too many domains wait for verification; POST /webhooks answers 403 at the webhook limit; POST /mailboxes answers 403 at the plan's user limit. |
| 422 | invalid_attachment | An attachment is malformed, or uses a path or URL instead of base64 content, or the attachments together exceed 20 MB after decoding ("Attachments exceed 20 MB"). |
| 429 | rate_limit_exceeded | Too many requests per second, or a sending limit was reached; also a metrics request of the company that is already running, or too many waiting webhook test events. |
| 429 | monthly_quota_exceeded | The plan's monthly email allowance is used up. |
| 500 | application_error | An unexpected error on our side. Details are never returned. |
| 502 | mail_server_error | The mail server refused a mailbox, alternate email or group change. The message carries only its error codes. |
Every authenticated endpoint can return the authentication errors (missing_api_key,
invalid_api_key, service_disabled, account_suspended, ip_not_allowed), the per-second
rate_limit_exceeded and application_error. The reference pages list the other errors of
each endpoint.
Pagination
List endpoints return a page of objects with cursor pagination:
| Name | Type | Description |
|---|---|---|
limit | integer | Number of objects to return, 1 to 100. Default 20. |
after | string | Id of an object from a previous page: return the objects that come after it in the list order. |
before | string | Id of an object from a previous page: return the objects that come before it in the list order. Use either after or before, not both. |
{
"object": "list",
"has_more": true,
"data": [ … ]
}
Most lists are ordered newest first, so to read the next (older) page pass the id of the last object as
after. has_more is true when more objects exist beyond this page in the direction
you are paging. A cursor must be the id of an object in the same list; anything else gives
422 validation_error ("Invalid pagination cursor"). Mailboxes and groups are ordered by address, and
their cursors are addresses.
curl "https://send.tsmart.ai/emails?limit=50&after=4ef9a417-02e9-4d39-ad75-9611e0fa6f60" \
-H "Authorization: Bearer $TSMART_API_KEY" Idempotency
POST /emails and POST /emails/batch accept an Idempotency-Key header (1 to 256
characters) so a retried request never sends twice. Keys are kept for 24 hours, separately per company and endpoint.
-
A retry with the same key and the same body gets the stored response, with the header
idempotent-replayed: true, and nothing is sent again. - The same key with a different body is refused with
409 invalid_idempotent_request. - A retry while the first request is still running is refused with
409 concurrent_idempotent_requests. - A request that fails (any status outside 2xx) releases its key, so it can be retried with the same key.
curl -X POST https://send.tsmart.ai/emails \
-H "Authorization: Bearer $TSMART_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: welcome-user/123" \
-d '{"from":"Acme <hello@yourdomain.com>","to":"jane@example.com","subject":"Welcome","text":"Hello!"}' Use a key that identifies the business event (an order number, a user id and a template name), not a random value per attempt.
Rate limits
Each company may make 10 requests per second across all of its keys (a sliding one-second window). Every
authenticated response carries the current state, except a restricted_api_key error, which still counts
against the rate:
| Header | Value |
|---|---|
ratelimit-limit | Requests allowed per window (10). |
ratelimit-remaining | Requests left in the current window. |
ratelimit-reset | Seconds until the window resets (always 1). |
retry-after | Only on a 429 for the request rate or for requests in progress: seconds to wait before retrying. |
Sending is also limited by recipients (each address in to, cc and bcc counts):
450 per company in any hour (a fair share of the platform's capacity), at most 20,000 recipients waiting in scheduled
emails, and the monthly allowance of your plan. These answer 429 rate_limit_exceeded or
429 monthly_quota_exceeded without retry-after. A company may also have at most 25 requests
in progress, and one request body over 2 MB at a time; these answer 429 rate_limit_exceeded with
retry-after. See Limits and errors.
A shared platform-wide hourly cap can also answer 429 rate_limit_exceeded ("Hourly sending limit
reached") before your own limits are reached; retry later.
Timestamps and ids
Timestamps in API objects are UTC with microseconds, in the form 2026-10-12 09:15:02.481233+00. Webhook
payloads, webhook delivery attempts (sent_at), the created_at of
GET /webhooks/{id} and the range of GET /emails/metrics use ISO 8601 with a
Z, for example 2026-10-12T09:15:02.481Z. Inputs such as scheduled_at take ISO 8601
with a time zone.
Emails, domains, API keys, webhooks, suppressions and log entries have UUID ids. Email events have numeric string ids;
webhook events have ids that start with msg_. Mailboxes, alternate emails and groups are named by their
address, URL-encoded in the path.
Resources
The Node.js SDK, the Python SDK and the CLI cover every endpoint; each reference page lists the matching method and command.