Skip to content
SecureThreadDevelopers

API reference

Send published templates from your own systems

The public API lets an integration keep the owner’s contacts up to date, list the templates and mailboxes the owner may use, send a message from a published template, and check on that message. Every call is authenticated by a workspace API key.

Keys act as their owner

A key can do only what the person who created it may do, and only within the scopes it was given. Administrative powers never pass to a key.

Four scopes, chosen per key

Give a key read, templates, contacts or send access, or any mix of them. A route refuses a key that lacks the scope it needs.

Sends from published templates

A send uses the published version of a template and its variable values. The request cannot change the subject, preview text or body.

Safe retries

With the same Idempotency-Key and the same request, a retry returns the first answer, and the message is not sent twice.

Status that says what is known

A message moves through statuses such as QUEUED, ACCEPTED and FAILED. ACCEPTED means the provider accepted the message, not that the recipient has it.

Budgets for every key

Each key has its own budget over a rolling five-minute window. Sends also count against the owner’s own send budget, which the web app shares.

Try it

Send from your own code

One request sends a published template through a connected mailbox, with every check and approval the app applies. Pick your language.

Send a message from a published templateShell
curl -X POST https://app.securethread.co/api/v1/public/messages \  -H "Authorization: Bearer $SECURETHREAD_API_KEY" \  -H "Content-Type: application/json" \  -H "Idempotency-Key: onboarding-casey-0001" \  -d '{    "templateId": "<template-id>",    "connectionId": "<mailbox-id>",    "to": [{ "email": "[email protected]", "name": "Casey Example" }],    "variables": { "first_name": "Casey" }  }'

01

Authentication

Every call is authenticated by a workspace API key, sent as a bearer token. Cookies and sessions are never accepted on these routes. GET /api/v1/public/whoami answers with the key’s workspace, owner and scopes, which makes it a useful first check that a key works.

  • A key starts with stk_. Its secret is shown once, when an administrator creates the key under Administration › API keys. Only a hash of the secret is kept, so store the secret the way you store a password.
  • The Authorization scheme is case-insensitive. A key in the query string is never accepted, because it would reach logs.
  • A key acts as the person who created it, the owner. It can do only what the owner may do, and only within its scopes.
  • An administrator’s own administrative powers never pass to a key, so a key created by an administrator still cannot change settings.
  • A key works only inside the workspace it was created in. Ids from another workspace answer 404.
Check that a key worksShell
curl https://app.securethread.co/api/v1/public/whoami \  -H "Authorization: Bearer stk_<your-key>"

02

Scopes and access limits

Each key carries one or more scopes. A route refuses a key that lacks its scope with 403.

  • A key can be limited to address ranges, written in CIDR form such as 203.0.113.0/24. A call from any other address is refused with 403, whatever the scopes.
  • A key can expire after 30, 90 or 365 days, or never. A revoked or expired key answers 401 at once.
  • Revoking a key takes effect on its next call. If the owner’s account is deactivated, every key the owner created stops working.

read

Routes it opens

GET /api/v1/public/messages/{id}

Owner capability it uses

Sent-mail history (own)

templates

Routes it opens

GET /api/v1/public/templates

Owner capability it uses

Template use (shared)

contacts

Routes it opens

GET and POST /api/v1/public/contacts

Owner capability it uses

Manage own contacts

send

Routes it opens

POST /api/v1/public/messages and GET /api/v1/public/mailboxes

Owner capability it uses

Send email, and use of the shared templates a send names

03

Templates, mailboxes and contacts

These routes list the templates and mailboxes the owner may use before a send, and let an integration manage the owner’s own contacts. The examples use [email protected] and Alex Example as sample people, and the ids are placeholders.

  • GET /api/v1/public/templates needs the templates scope. It lists the published templates the owner may use, with the variables of each published version. Only metadata leaves the server: never the subject, preview text or body.
  • Templates take q (search text, up to 120 characters), page (default 1) and pageSize (default 25, up to 100). Each item has an id, name, description, variables and updatedAt.
  • GET /api/v1/public/mailboxes needs the send scope. It lists the mailboxes the owner may send from right now: their own and the shared ones granted to them. A mailbox that is signed out or suspended is left out. The id is the connectionId a send names. No server, sign-in or health detail is returned.
  • GET /api/v1/public/contacts needs the contacts scope. Its q parameter matches the address, first name, last name or company, and it takes page and pageSize.
  • POST /api/v1/public/contacts needs the contacts scope. It creates the owner’s contact for an address, or updates the contact the owner already has. It answers 201 with the contact when it is created and 200 when an existing contact is updated.
  • A contact has an id, email, first name, last name, company, job title, notes, createdAt and updatedAt. Addresses are matched without regard to case, and only the fields you send change.
  • Contacts made through the API are personal. The key cannot make a shared contact, and a visibility field is refused with 400. Each contact change is audited under the owner, as it is in the web app.
Templates, mailboxes and contactsShell
# Published templates the owner may usecurl "https://app.securethread.co/api/v1/public/templates?q=interview&pageSize=10" \  -H "Authorization: Bearer stk_<your-key>" # Mailboxes the owner may send fromcurl https://app.securethread.co/api/v1/public/mailboxes \  -H "Authorization: Bearer stk_<your-key>" # Contacts the owner managescurl "https://app.securethread.co/api/v1/public/contacts?q=Example&pageSize=25" \  -H "Authorization: Bearer stk_<your-key>" # Create a contact, or update the one the owner already hascurl -X POST https://app.securethread.co/api/v1/public/contacts \  -H "Authorization: Bearer stk_<your-key>" \  -H "Content-Type: application/json" \  -d '{ "email": "[email protected]", "firstName": "Alex", "company": "Example Ltd" }'

04

Send a message

POST /api/v1/public/messages needs the send scope. It sends one message from a template’s published version, through the same send path the web composer uses. The subject, preview text and body come from the template, and the request cannot set them. Send a unique Idempotency-Key with each message, as described under Idempotency and rate limits.

  • 201 Created means the message is with the sending queue. The answer has status QUEUED, the messageId, and a Location header that points to GET /api/v1/public/messages/{messageId}.
  • 202 Accepted means the owner’s account requires an administrator’s approval first. The answer has status PENDING_APPROVAL, a null messageId and an approvalRequestId. Nothing is queued until a different administrator approves the request.
  • Neither answer says that the recipient has the message. Check the status with GET /api/v1/public/messages/{id}.
  • Every send gate applies as it does in the web app: the mailbox’s access, the organization’s sending domains, the sandbox, approvals, the content policy, the plan’s limits and the send rate limits. A send refused by one of them returns the same error the web app would show, with its status code.
  • The approval rules for AI assistant sends apply to assistants, not to API keys. An account that needs approval for every send is held whichever way it sends.

templateId

Required

Yes

Notes

A published template the owner may use.

connectionId

Required

Yes

Notes

A mailbox the owner may send from, as listed by GET /api/v1/public/mailboxes.

to

Required

Yes

Notes

One to 50 recipients, each with an email and an optional name.

variables

Required

No

Notes

Values by variable key, from the template’s variables list.
Send a message from a published templateShell
curl -X POST https://app.securethread.co/api/v1/public/messages \  -H "Authorization: Bearer stk_<your-key>" \  -H "Content-Type: application/json" \  -H "Idempotency-Key: <unique-key-per-message>" \  -d '{    "templateId": "<template-id>",    "connectionId": "<mailbox-id>",    "to": [{ "email": "[email protected]", "name": "Casey Example" }],    "variables": { "first_name": "Casey", "interview_date": "Monday 3 November" }  }'

05

Message status

GET /api/v1/public/messages/{id} needs the read scope. It returns the status of a message the owner sent. Another person’s message, or one in another workspace, answers 404.

  • acceptedAt is when the provider accepted the message. Accepted is not proof that the recipient has it. Nothing on this endpoint reports what happens after that.
  • failureReason is set while a send is failing or being tried again. It is written in plain English, never in the provider’s own words.
  • When the outcome is not known, failureReason says that the provider may have accepted the message. Check with the recipient before sending it again.
  • To learn about changes as they happen, subscribe to event webhooks instead of polling. Each webhook call carries webhook-id, webhook-timestamp and webhook-signature. Check the signature against the endpoint’s secret before trusting a payload.

QUEUED

What it means

The message is with the sending queue.

SENDING

What it means

The send is in progress.

ACCEPTED

What it means

The provider accepted the message, and acceptedAt records when. Being accepted by the provider is not proof that the recipient has the message.

RETRYING

What it means

The send is failing and is being tried again. failureReason says why.

FAILED

What it means

The send failed. failureReason explains it in plain English.

CANCELLED

What it means

The send was canceled.
Response to GET https://app.securethread.co/api/v1/public/messages/<message-id>JSON
{  "id": "<message-id>",  "status": "FAILED",  "createdAt": "2026-10-25T09:31:02.000Z",  "acceptedAt": null,  "recipientCount": 1,  "failureReason": "SecureThread could not sign in to the mailbox. Reconnect the mailbox, then send the message again."}

06

Idempotency and rate limits

Send a unique Idempotency-Key with each message, so that a retry cannot send the message twice. Rate limits are counted per API key over a rolling five-minute window, and a call over a budget answers 429 RATE_LIMITED with a Retry-After header.

  • Use 8 to 128 visible ASCII characters for the key, such as a UUID.
  • A retry with the same key and the same request returns the first answer, with the header Idempotent-Replayed: true. The message is not sent twice.
  • The same key with a different request is refused with 422 IDEMPOTENCY_KEY_REUSED. Use a new key for new content.
  • A retry that arrives while the first call is still being handled answers 409 IDEMPOTENCY_IN_PROGRESS with Retry-After. Wait that many seconds, then retry with the same key.
  • The key is remembered for 24 hours and belongs to the key that sent it.
  • Without the header, the message is still sent once, but a retry sends it again. Send the header from any automation that retries.
  • A presented key that does not exist is also limited per client address, so guessing keys cannot run without limit.
  • A send also counts against the owner’s own send budget of 120 calls per five minutes, which the web app’s sends share. The owner’s keys together cannot send more than the owner could from the web app in the same window.

Shared budget

Routes

Every public route

Limit

600 calls

Templates

Routes

GET /api/v1/public/templates

Limit

120 calls

Contacts

Routes

GET and POST /api/v1/public/contacts

Limit

120 calls

Mailboxes

Routes

GET /api/v1/public/mailboxes

Limit

120 calls

Sends

Routes

POST /api/v1/public/messages

Limit

60 calls

Message status

Routes

GET /api/v1/public/messages/{id}

Limit

300 calls

07

Errors

Every error uses the same envelope. Each message is written for a person, and the details array, when present, names the field that needs attention.

400

Code

VALIDATION_FAILED

Meaning

A field is missing or wrong, an unknown field was sent, or the Idempotency-Key header is not 8 to 128 visible characters.

401

Code

UNAUTHENTICATED

Meaning

No key, an unknown key, a revoked key or an expired key.

403

Code

FORBIDDEN

Meaning

The key lacks the scope, the address is not allowed, or the owner lost the access the scope needs.

404

Code

NOT_FOUND

Meaning

The id does not exist, belongs to someone else, or is in another workspace.

409

Code

CONFLICT

Meaning

The template has no published version, or the request conflicts with the current state.

409

Code

IDEMPOTENCY_IN_PROGRESS

Meaning

A send with the same key is still being handled. Wait Retry-After seconds and retry.

422

Code

IDEMPOTENCY_KEY_REUSED

Meaning

The key was used before for a different request.

422

Code

CONTENT_REJECTED

Meaning

The template’s content cannot be sent as it stands.

422

Code

SENDER_NOT_ALLOWED

Meaning

The mailbox cannot send for this owner.

429

Code

RATE_LIMITED

Meaning

The key has used its budget for this kind of call. Wait Retry-After seconds.

429

Code

QUOTA_EXCEEDED

Meaning

The plan’s or the organization’s sending limit is reached.

503

Code

MAINTENANCE

Meaning

SecureThread is paused for maintenance. Requests that only look things up still work; sends wait.
Error envelopeJSON
{  "error": {    "code": "FORBIDDEN",    "message": "This API key does not have the scope this action needs.",    "details": []  },  "requestId": "req_example",  "timestamp": "2026-10-25T09:30:00.000Z"}

08

Versions and stability

The routes on this page are the stable surface of the public API. They all live under /api/v1/public/, and a version path marks any change that would break them.

  • A change that would break a stable route, such as removing a field, a route or a status code, needs a new version path.
  • Adding an optional field is not a breaking change, so ignore fields you do not know.
Is sending through the API the same as the web composer?

Sends use the same send path as the web composer, and every send gate applies as it does in the web app. A send that is refused returns the same error the web app would show, with its status code.

Can a key set the subject or body of a message?

No. The subject, preview text and body come from the template’s published version. A request names the template, the mailbox, the recipients and the variable values.

Does a 201 or 202 response mean the recipient has the message?

No. A 201 means the message is with the sending queue. A 202 means an administrator’s approval is needed first. Neither answer says that the recipient has the message, so check the status with GET /api/v1/public/messages/{id}.

Can an API key change settings?

No. An administrator’s own administrative powers never pass to a key, so a key created by an administrator still cannot change settings.

Can the secret of a key be shown again?

No. The secret is shown once, when an administrator creates the key under Administration › API keys, and only a hash of it is kept.

Build on SecureThread

Create a workspace key, send from a published template, and get signed events back. Every API send passes the same checks and approvals as one from the app.

Send from the mailboxes you already useA person approves what needs approvalCredentials encrypted with AES-256-GCMEvery action in an append-only audit logAI assistants governed over MCP1,499 starter templatesHonest status: accepted is never called delivered