Authentication
Koltrix has two authentication surfaces and the right one depends on what you're doing.
| Surface | Auth | Used by |
|---|---|---|
app.koltrix.com (dashboard) | Email + password / SSO via your account login | Humans in a browser |
/api/v2/* (HTTP API) | Bearer API key — Authorization: Bearer kx_... | Your server-side code |
/api/v1/public/* | Per-list public token in the URL (no Authorization header) | Embed signup forms on customer sites |
| SMTP relay (port 2525) | AUTH PLAIN with your API key as the password | Mail libraries (nodemailer, swaks) |
The split exists so we can keep two very different threat models clean.
The dashboard is talking to a logged-in human; the public HTTP API is
talking to your server and trusts nothing except a hashed, scope-limited
API key. The public-token surface is no-auth on purpose — it's the URL
that ships inside an HTML <form action="..."> on your landing page,
protected with a honeypot field instead of a header.
API keys (/api/v2/*)
Create one at app.koltrix.com/api-keys (or Settings → API Keys → New key). Each key is:
- Scoped to one organisation. The tenant is bound at creation and cannot be changed.
- Prefixed
kx_so it's obvious in logs and easy to grep for. - SHA-256 hashed at rest. We show the full secret exactly once when you create it — copy it somewhere safe, we cannot recover it.
Scopes
Tick one or more when creating the key — least privilege wins:
| Scope | What it lets the key do |
|---|---|
read | List and read messages, lists, sequences, events |
send | Send transactional email (REST + SMTP) |
newsletter | Manage lists, subscribers, segments, broadcasts, sequences |
contacts | Manage CRM contacts |
* | Full access — every endpoint, every method. Use sparingly. |
A key may carry several scopes; the endpoint checks whichever it needs.
A request that hits a route the key isn't scoped for returns 403.
Make an authenticated request
curl https://api.koltrix.com/api/v2/me \
-H "Authorization: Bearer kx_..."{
"tenant": "acme",
"permissions": ["read", "send", "newsletter"]
}Permission errors
If the key is valid but missing the scope you need, the API returns
403:
{ "error": "missing permission: newsletter" }If the key was revoked, deleted, or never existed, the API returns
401:
{ "error": "invalid or revoked api key" }Rotation
There's no clever "rotate in place" — mint a new key, deploy it
everywhere the old one was used, then revoke the old key in
Settings → API Keys. Revocation takes effect immediately; the next
request with the old key returns 401. Anything already in flight
finishes without interruption because we only check revocation at
request entry.
Rotate quarterly as routine hygiene, or immediately if a key has been committed to git, posted in chat, or printed in a log.
Public list tokens (embed forms)
Some routes — embed signup, unsubscribe confirmation — must be callable from a browser with no auth. We support that without exposing your API key by issuing a per-list public token that's baked into the form action URL:
<form action="https://api.koltrix.com/api/v1/public/lists/PUBLIC_TOKEN/subscribe"
method="post">
<input name="email" type="email" required />
<input name="_hp" type="text" style="display:none" tabindex="-1" autocomplete="off" />
<button>Subscribe</button>
</form>The _hp field is a honeypot — bots fill every input on the page
including hidden ones, so a non-empty _hp value makes us silently
200 without saving. This keeps the form working without a CAPTCHA and
without coupling to a third-party anti-bot vendor.
If you suspect a token has leaked (somebody screenshotted your landing page into a guide?), rotate it under Newsletters → list → Embed → Rotate. The old token starts returning errors immediately; the new one slots into the same form HTML.
SMTP authentication
The SMTP relay on smtp.koltrix.com:2525 accepts AUTH PLAIN and
AUTH LOGIN. Use any value for the username (apikey is conventional);
the password must be your kx_ API key with the send scope.
import smtplib, os
with smtplib.SMTP("smtp.koltrix.com", 2525) as s:
s.login("apikey", os.environ["KOLTRIX_KEY"])
# ... send messageIf the password doesn't parse as a kx_ key, the relay rejects with
535 5.7.8 Authentication failed. If the key parses but lacks the
send scope, you'll see the same 535 after RCPT TO.
See SMTP relay for full client examples.
Rate limits
| Limit | Default | Returned when exceeded |
|---|---|---|
| Per-IP (all surfaces) | 300 / minute | 429 |
Per-key (/api/v2) | 60 / minute | 429 |
| Per-account sends | warmup-driven | 429 |
Idempotency-Key replays don't count toward your quota. If you're consistently hitting the limit on a legitimate workload, contact [email protected] — we'll raise it on the account.