API reference
Domains
You send from your own domains. Add a domain, prove you own it with a TXT record, publish its SPF and DKIM records, and it is ready to send.
Every endpoint can also return the common errors listed in the overview, and all of them
need a full-access key (a sending-access key gets 401 restricted_api_key). For the DNS steps in detail
see Domains and DNS.
| Endpoint | Node.js | Python | CLI |
|---|---|---|---|
| GET /domains | domains.list | domains.list | tsmart domains list |
| POST /domains | domains.create | domains.create | tsmart domains create |
| GET /domains/{id} | domains.get | domains.get | tsmart domains get |
| PATCH /domains/{id} | domains.update | domains.update | tsmart domains tracking |
| DELETE /domains/{id} | domains.remove | domains.remove | tsmart domains delete |
| POST /domains/{id}/verify | domains.verify | domains.verify | tsmart domains verify |
The domain object
| Name | Type | Description |
|---|---|---|
object | string | Always "domain". |
id | string | Domain id (UUID). |
name | string | The domain name, lowercase. |
status | string | not_started: ownership not proven and not checked yet. pending: waiting for DNS records. partially_verified: some of the sending records (SPF, DKIM) were found at the last check. verified: SPF and DKIM were found at the last check and the domain can send. |
created_at | string | When the domain was added. |
region | string | Where mail is processed: "eu-central". |
capabilities | object | sending: "enabled" once ownership is proven, else "disabled". receiving: "enabled" when the domain also receives mail in the company's mailboxes, else "disabled". |
open_tracking | boolean | Single domain and create only. Whether HTML emails from this domain get an open-tracking pixel. Default false. |
click_tracking | boolean | Single domain and create only. Whether links in HTML emails from this domain are tracked. Default false. |
records | object[] | Single domain and create only. The DNS records to publish, see below. |
Each entry of records:
| Name | Type | Description |
|---|---|---|
record | string | What the record is for: Ownership, SPF, DKIM, DMARC or MX. |
name | string | The host name to publish the record at. |
type | string | TXT or MX. |
ttl | string | Always "Auto": use your DNS provider's default. |
status | string | verified when the record was found with the expected value at the last check, else pending. |
value | string | The value to publish. Empty while the DKIM key is being prepared. |
priority | integer | MX records only. |
Until ownership is proven, records holds only the Ownership TXT record. After that it lists SPF and DKIM
(both needed to send), DMARC (recommended) and MX.
GET /domains
Lists the company's domains, newest first.
Permission: full access; the key's creator needs the API controls privilege.
Query
| Name | Type | Description |
|---|---|---|
limit | integer | 1 to 100, default 20. |
after | string | A domain id: return older domains. |
before | string | A domain id: return newer domains. Use either after or before. |
Request
curl https://send.tsmart.ai/domains \
-H "Authorization: Bearer $TSMART_API_KEY" Response
List entries have no open_tracking, click_tracking or records.
{
"object": "list",
"has_more": false,
"data": [
{
"object": "domain",
"id": "a3b1c5d2-7e8f-4a6b-9c0d-1e2f3a4b5c6d",
"name": "yourdomain.com",
"status": "verified",
"created_at": "2026-10-09 08:15:02.123456+00",
"region": "eu-central",
"capabilities": { "sending": "enabled", "receiving": "disabled" }
}
]
} Errors
| Status | Name | When |
|---|---|---|
| 403 | invalid_permission | The key's creator lacks the API controls privilege. |
| 422 | validation_error | limit out of range, both after and before, or an invalid cursor. |
POST /domains
Adds a domain and returns the TXT record that proves you own it.
Permission: full access; the key's creator needs the Domains privilege.
Body
| Name | Type | Description |
|---|---|---|
name required | string | The domain, such as yourdomain.com or mail.yourdomain.com. It is lowercased, and a pasted scheme, path, port, trailing dot or user@ part is removed. |
Request
curl -X POST https://send.tsmart.ai/domains \
-H "Authorization: Bearer $TSMART_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "name": "yourdomain.com" }' const { data, error } = await tsmart.domains.create({ name: "yourdomain.com" }); domain = client.domains.create({"name": "yourdomain.com"}) Response
{
"object": "domain",
"id": "a3b1c5d2-7e8f-4a6b-9c0d-1e2f3a4b5c6d",
"name": "yourdomain.com",
"status": "not_started",
"created_at": "2026-10-12 09:30:11.402871+00",
"region": "eu-central",
"capabilities": { "sending": "disabled", "receiving": "disabled" },
"open_tracking": false,
"click_tracking": false,
"records": [
{
"record": "Ownership",
"name": "_tsmart-verify.yourdomain.com",
"type": "TXT",
"ttl": "Auto",
"status": "pending",
"value": "tsmart-verify=Qm8x2LkP0aZ7vB1nR4tYc9Wd"
}
]
}
Publish the Ownership record, then call POST /domains/{id}/verify.
A domain whose ownership is not proven within 7 days of when the company first added it expires and has to be added
again; deleting and re-adding it does not restart the 7 days. A domain.created webhook event is sent.
Errors
| Status | Name | When |
|---|---|---|
| 403 | invalid_permission | The key's creator lacks the Domains privilege. |
| 422 | missing_required_field | name is missing or empty. |
| 422 | validation_error | name is not a valid domain name. |
| 403 | validation_error | "This domain cannot be added": a public mailbox provider, an example domain or a platform domain. "The … domain has been registered already.": the domain is already in use. "The … domain has been added by another company that has not proven ownership yet …": another company's claim holds it; the message gives the time (UTC, ISO 8601) from which it can be added. "This company's claim of … expired …": your company's claim lapsed while another company was waiting for the domain; the message gives the time from which it can be added again. "The company's plan does not allow more domains". |
| 403 | account_inactive | The company's subscription is not active. |
| 429 | validation_error | 10 domains of the company are already waiting for ownership verification. |
GET /domains/{id}
Returns one domain with its status, tracking settings and DNS records.
Permission: full access; the key's creator needs the API controls privilege.
Path
| Name | Type | Description |
|---|---|---|
id required | string | The domain id. |
Request
curl https://send.tsmart.ai/domains/a3b1c5d2-7e8f-4a6b-9c0d-1e2f3a4b5c6d \
-H "Authorization: Bearer $TSMART_API_KEY" Response
A domain whose ownership is proven, with SPF published and DKIM still missing:
{
"object": "domain",
"id": "a3b1c5d2-7e8f-4a6b-9c0d-1e2f3a4b5c6d",
"name": "yourdomain.com",
"status": "partially_verified",
"created_at": "2026-10-12 09:30:11.402871+00",
"region": "eu-central",
"capabilities": { "sending": "enabled", "receiving": "disabled" },
"open_tracking": false,
"click_tracking": false,
"records": [
{
"record": "SPF",
"name": "yourdomain.com",
"type": "TXT",
"ttl": "Auto",
"status": "verified",
"value": "v=spf1 ip4:91.99.133.27 ~all"
},
{
"record": "DKIM",
"name": "ts2610._domainkey.yourdomain.com",
"type": "TXT",
"ttl": "Auto",
"status": "pending",
"value": "v=DKIM1;k=rsa;t=s;s=email;p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA..."
},
{
"record": "DMARC",
"name": "_dmarc.yourdomain.com",
"type": "TXT",
"ttl": "Auto",
"status": "pending",
"value": "v=DMARC1; p=none; rua=mailto:reports@system.tsmart.ai; fo=1"
},
{
"record": "MX",
"name": "yourdomain.com",
"type": "MX",
"ttl": "Auto",
"status": "pending",
"value": "mail.tsmart.ai.",
"priority": 10
}
]
} The values above are examples: always publish the values from your own response. If your domain already has an SPF record, the SPF value is your record with the sending server added, so you keep one SPF record.
Errors
| Status | Name | When |
|---|---|---|
| 403 | invalid_permission | The key's creator lacks the API controls privilege. |
| 404 | not_found | No domain with this id in your company ("Domain not found"). |
PATCH /domains/{id}
Switches open and click tracking for emails sent from the domain.
Permission: full access; the key's creator needs the Domains privilege.
Path
| Name | Type | Description |
|---|---|---|
id required | string | The domain id. |
Body
| Name | Type | Description |
|---|---|---|
open_tracking | boolean | Add a 1×1 tracking image to HTML emails, which records email.opened events. |
click_tracking | boolean | Send links in HTML emails through https://send.tsmart.ai/t/c/…, which records email.clicked events and leads to the original URL for 180 days after the email was accepted. |
Omitted fields keep their value; a body without either field changes nothing. Tracking applies to emails sent after the
change, and only to their HTML part. Tracked links are signed: they can only lead to the URL your email contained.
Links to your company's verified domains (and their subdomains) redirect at once; links to any other website first
show a short page on send.tsmart.ai that names the destination, and the reader continues from there.
Request
curl -X PATCH https://send.tsmart.ai/domains/a3b1c5d2-7e8f-4a6b-9c0d-1e2f3a4b5c6d \
-H "Authorization: Bearer $TSMART_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "open_tracking": true, "click_tracking": true }' Response
{
"object": "domain",
"id": "a3b1c5d2-7e8f-4a6b-9c0d-1e2f3a4b5c6d"
} A change sends a domain.updated webhook event.
Errors
| Status | Name | When |
|---|---|---|
| 403 | invalid_permission | The key's creator lacks the Domains privilege. |
| 404 | not_found | No domain with this id in your company. |
| 422 | validation_error | open_tracking or click_tracking is not a boolean. |
DELETE /domains/{id}
Removes a domain from the company. Emails can no longer be sent from it.
Permission: full access; the key's creator must be the company owner.
Path
| Name | Type | Description |
|---|---|---|
id required | string | The domain id. |
Request
curl -X DELETE https://send.tsmart.ai/domains/a3b1c5d2-7e8f-4a6b-9c0d-1e2f3a4b5c6d \
-H "Authorization: Bearer $TSMART_API_KEY" Response
{
"object": "domain",
"id": "a3b1c5d2-7e8f-4a6b-9c0d-1e2f3a4b5c6d",
"deleted": true
} A domain.deleted webhook event is sent.
Errors
| Status | Name | When |
|---|---|---|
| 403 | invalid_permission | The key's creator is not the company owner. |
| 404 | not_found | No domain with this id in your company. |
| 409 | resource_in_use | The domain still has mailboxes; remove them in the console first. |
POST /domains/{id}/verify
Checks the domain's DNS: the first time it looks for the Ownership TXT record and, once found, sets the domain up for sending; after that it checks the SPF, DKIM, DMARC and MX records again.
Permission: full access; the key's creator needs the Domains privilege.
Path
| Name | Type | Description |
|---|---|---|
id required | string | The domain id. |
Request
curl -X POST https://send.tsmart.ai/domains/a3b1c5d2-7e8f-4a6b-9c0d-1e2f3a4b5c6d/verify \
-H "Authorization: Bearer $TSMART_API_KEY" Response
{
"object": "domain",
"id": "a3b1c5d2-7e8f-4a6b-9c0d-1e2f3a4b5c6d"
}
The check runs during the call. Read the result with GET /domains/{id}: after
ownership is proven, records lists the SPF and DKIM records to publish. Publish them and call verify again
until status is verified. When the status changes, a domain.updated webhook event
is sent.
Sending uses the stored result of the last check, not a live DNS lookup: after you publish or fix the records, call
verify again before sending. The platform also re-checks verified domains periodically, so a record that is removed
later changes the status and stops sending from the domain. Status changes found by this periodic check send no
domain.updated event.
Errors
| Status | Name | When |
|---|---|---|
| 403 | invalid_permission | The key's creator lacks the Domains privilege. |
| 404 | not_found | No domain with this id in your company. |
| 422 | validation_error | The Ownership record was not found yet ("The TXT record _tsmart-verify.… was not found yet"), or the domain could not be set up for sending. |