Authentication & API Keys
Manage API keys and authenticate requests
All Tratto API requests require authentication via bearer tokens (API keys).
API Key Format
Tratto API keys follow this format:
tratto_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
tratto_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx- Prefix:
tratto_live_(real delivery) ortratto_test_(simulated — see Test mode) - Followed by 32 random characters
- Used as a bearer token in the
Authorizationheader
The environment is baked into the key: a test key runs the full pipeline without sending real email, sees only test data, and is rejected by endpoints that reach real recipients.
Authentication Header
Include your API key in every request:
Authorization: Bearer tratto_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxExample:
curl https://api.tratto.email/v1/emails \
-H "Authorization: Bearer tratto_live_..."Without the header, you'll receive 401 Unauthorized.
Permissions
Every key carries a set of permissions, and they are enforced on each request: a key missing the permission an endpoint requires gets 403 Forbidden, no matter which workspace it belongs to.
| Permission | Grants |
|---|---|
emails:send | Send email, schedule, cancel, reschedule |
emails:read | Read the email log and delivery events |
contacts:read / contacts:write | Read / create, update, import contacts and audiences |
domains:read / domains:write | Read / add, verify, remove sending domains |
templates:read / templates:write | Read / create and edit templates |
campaigns:read / campaigns:write | Read / create and send campaigns and flows |
webhooks:read / webhooks:write | Read / register, edit, rotate webhook endpoints |
api-keys:read / api-keys:write | Read / create, update, revoke API keys |
billing:read / billing:write | Read plan and usage / start checkout, open the billing portal |
workspace:write | Change workspace settings, including the default sender |
members:write | Invite, remove, and change the role of workspace members |
* | Full access — every permission above, including future ones |
Grant the narrowest set that does the job. A key that only sends transactional email needs emails:send and nothing else; one that also mirrors signups into your contact list needs contacts:write too.
* is stored on its own: listing it alongside granular permissions is redundant, since it already covers them.
Create an API Key
Generate a new API key via the API, or from Settings → API keys in the dashboard.
name, env and permissions are all required. Omitting permissions (or passing an empty array) returns 422.
cURL
curl -X POST https://api.tratto.email/v1/api-keys \
-H "Authorization: Bearer tratto_live_EXISTING_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"production","env":"live","permissions":["emails:send"]}'Node.js
const response = await fetch('https://api.tratto.email/v1/api-keys', {
method: 'POST',
headers: {
'Authorization': 'Bearer tratto_live_EXISTING_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({ name: 'production', env: 'live', permissions: ['emails:send'] }),
});
const { data } = await response.json();
console.log('New API Key:', data.key);Python
import requests
response = requests.post(
'https://api.tratto.email/v1/api-keys',
headers={
'Authorization': 'Bearer tratto_live_EXISTING_KEY',
'Content-Type': 'application/json',
},
json={'name': 'production', 'env': 'live', 'permissions': ['emails:send']},
)
api_key = response.json()['data']['key']
print(f"New API Key: {api_key}")Response:
{
"data": {
"id": "key_abc123",
"name": "production",
"key": "tratto_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"prefix": "tratto_live_xxxx...xxxx",
"env": "live",
"permissions": ["emails:send"],
"createdAt": "2025-06-30T12:00:00Z",
"lastUsedAt": null,
"revokedAt": null
}
}⚠️ Important: The key field is shown only once. Save it immediately: it won't be displayed again. If you lose it, revoke and create a new key.
List API Keys
Retrieve all API keys for your tenant.
cURL
curl https://api.tratto.email/v1/api-keys \
-H "Authorization: Bearer tratto_live_..."Node.js
const response = await fetch('https://api.tratto.email/v1/api-keys', {
headers: {
'Authorization': 'Bearer tratto_live_...',
},
});
const { data } = await response.json();
console.log(data); // Array of API key objectsPython
import requests
response = requests.get(
'https://api.tratto.email/v1/api-keys',
headers={'Authorization': 'Bearer tratto_live_...'},
)
keys = response.json()['data']
for key in keys:
print(f"Key: {key['prefix']} (Created: {key['createdAt']})")Response:
{
"data": [
{
"id": "key_abc123",
"name": "production",
"prefix": "tratto_live_xxxx...xxxx",
"createdAt": "2025-06-30T12:00:00Z",
"lastUsedAt": "2025-06-30T13:45:00Z"
},
{
"id": "key_def456",
"name": "development",
"prefix": "tratto_live_yyyy...yyyy",
"createdAt": "2025-06-29T10:00:00Z",
"lastUsedAt": "2025-06-29T15:20:00Z"
}
]
}Note: The full key is never returned after creation, only the prefix (first and last 4 characters).
lastUsedAt is null until the key authenticates a request for the first time. It is refreshed at most once every 15 minutes, so it answers "is this key still in use?" rather than giving an exact timestamp of the last call.
Update Key Permissions
Change the permissions of an existing key, without revoking it or rotating the secret. The key value itself never changes, so nothing that already uses it needs redeploying — permissions are read on every request, and the new set applies to the next one.
cURL
curl -X PATCH https://api.tratto.email/v1/api-keys/key_abc123 \
-H "Authorization: Bearer tratto_live_..." \
-H "Content-Type: application/json" \
-d '{"permissions":["emails:send","contacts:write"]}'Node.js
const response = await fetch('https://api.tratto.email/v1/api-keys/key_abc123', {
method: 'PATCH',
headers: {
'Authorization': 'Bearer tratto_live_...',
'Content-Type': 'application/json',
},
body: JSON.stringify({ permissions: ['emails:send', 'contacts:write'] }),
});
const { data } = await response.json();
console.log('Permissions:', data.permissions);Python
import requests
response = requests.patch(
'https://api.tratto.email/v1/api-keys/key_abc123',
headers={
'Authorization': 'Bearer tratto_live_...',
'Content-Type': 'application/json',
},
json={'permissions': ['emails:send', 'contacts:write']},
)
print(f"Permissions: {response.json()['data']['permissions']}")permissions replaces the previous set — it is not merged into it. Send the complete list you want the key to end up with.
Requires api-keys:write. Other responses:
| Status | When |
|---|---|
409 | The key is revoked — permissions can no longer be changed. Create a new key instead. |
422 | An unknown permission, an empty list, or an attempt to remove api-keys:write from the key making the request. |
You cannot strip api-keys:write from the key you are authenticating with: no other endpoint could grant it back, so you would lock yourself out of key management. Removing it from a different key is allowed — that is recoverable.
Revoke an API Key
Immediately revoke an API key to stop it from working.
cURL
curl -X DELETE https://api.tratto.email/v1/api-keys/key_abc123 \
-H "Authorization: Bearer tratto_live_..."Node.js
const response = await fetch('https://api.tratto.email/v1/api-keys/key_abc123', {
method: 'DELETE',
headers: {
'Authorization': 'Bearer tratto_live_...',
},
});
const { data } = await response.json();
console.log('Revoked:', data.id);Python
import requests
response = requests.delete(
'https://api.tratto.email/v1/api-keys/key_abc123',
headers={'Authorization': 'Bearer tratto_live_...'},
)
print(f"Revoked: {response.json()['data']['id']}")Once revoked, requests with that key will return 401 Unauthorized.
Key Security Best Practices
1. Never Expose Keys Client-Side
API keys grant full workspace access. Never include them in:
- Frontend JavaScript (React, Vue, Angular)
- Mobile apps (iOS, Android)
- Published source code on GitHub
Instead, call your backend API, which holds the key securely.
For framework-specific guidance see React (Server Components and Server Actions) and Angular (SSR and AnalogJS/Nitro).
Wrong:
// ❌ NEVER DO THIS
const trattKey = 'tratto_live_...';
const response = await fetch('https://api.tratto.email/v1/emails', {
headers: { 'Authorization': `Bearer ${trattoKey}` },
});Right:
// ✅ Call your backend
const response = await fetch('/api/send-email', {
method: 'POST',
body: JSON.stringify({ to, subject, text }),
});2. Treat Keys Like Passwords
- Store in environment variables (
.env.local, not.env) - Use secrets management for production (GitHub Secrets, HashiCorp Vault, etc.)
- Don't commit them to Git
3. Rotate Keys Regularly
- Create a new key
- Update your services to use the new key
- Wait for all deployments to finish
- Revoke the old key
4. Use Specific Keys for Environments
Create separate keys for:
production: Used only in productionstaging: Used only in stagingdevelopment: Used only in local development
This way, a leaked development key doesn't compromise production.
5. Monitor Key Usage
Check lastUsedAt in list responses to identify unused keys. Revoke old, unused keys periodically.
Error Responses
401 Unauthorized
Cause: Missing or invalid API key.
{
"error": {
"code": "UNAUTHORIZED",
"message": "Missing or invalid Authorization header",
"docs": "https://docs.tratto.email/en/authentication"
}
}Fix:
- Ensure the
Authorization: Bearer tratto_live_...header is present - Check for typos in the API key
- Verify the key hasn't been revoked
403 Forbidden
Cause: API key is valid but doesn't have permission for this operation.
{
"error": {
"code": "FORBIDDEN",
"message": "Your API key does not have permission to perform this action",
"docs": "https://docs.tratto.email/en/authentication"
}
}The response names the permission that was missing, so you can tell which one to add:
{
"error": {
"code": "FORBIDDEN",
"message": "Missing required permission: contacts:write",
"suggestion": "Create a new API key with the 'contacts:write' permission."
}
}Fix it by granting that permission to the key — see Update Key Permissions — or by using a key that already has it. Check the key's current set with GET /v1/api-keys.
Idempotency Header
The Idempotency-Key header prevents duplicate requests on certain endpoints (POST operations that modify state).
Usage:
POST /v1/emails
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000Rules:
- Value should be a UUID v4 (RFC 4122)
- Tratto caches the response for 24 hours
- If you retry with the same key, you get the same response without a duplicate action
- The cached response is returned for any retry with that key; the request body is not compared, so reuse a key only for the operation that created it
Example:
import { v4 as uuidv4 } from 'uuid';
const idempotencyKey = uuidv4();
const response = await fetch('https://api.tratto.email/v1/emails', {
method: 'POST',
headers: {
'Authorization': 'Bearer tratto_live_...',
'Idempotency-Key': idempotencyKey,
},
body: JSON.stringify({
from: '[email protected]',
to: '[email protected]',
subject: 'Hello',
text: 'World',
}),
});See Idempotency for more details.
Next Steps
- Ready to send? Go to Send Email
- Need to verify domains? See Domains
- Understand all error codes? Check Error Codes
Edit this page on GitHub
Last updated on