Authentication
The Templatical SDK uses a backend-proxy pattern for authentication. Your API credentials (Client ID and Client Secret) stay on your server — the SDK only receives short-lived access tokens.
Overview
The authentication flow has three steps:
- Get API credentials — Create a project in the Templatical dashboard and note your Client ID and Client Secret
- Create a token endpoint — Build a backend endpoint that exchanges your credentials for an access token
- Configure the SDK — Point the SDK's
auth.urlto your token endpoint
The SDK calls your token endpoint automatically on initialization and refreshes the token before it expires (60 seconds before expiry).
Token Endpoint
Your backend needs an endpoint that:
- Calls the Templatical API with your Client ID, Client Secret, and a tenant identifier
- Returns the token response to the browser
API Request
Send a POST request to the Templatical token endpoint:
POST https://templatical.com/api/v1/auth/token
Content-Type: application/json
{
"client_id": "your-client-id",
"client_secret": "your-client-secret",
"tenant": "tenant-identifier"
}| Field | Type | Description |
|---|---|---|
client_id | string | Your project's Client ID |
client_secret | string | Your project's Client Secret |
tenant | string | A unique identifier for the tenant (user/organization) in your system |
The tenant field isolates data between different users or organizations in your application. Each tenant gets its own set of templates, media assets, and settings — they cannot access each other's data. Use any stable, unique string from your system as the tenant identifier, such as an organization ID, team slug, or user ID. For example, if your application has workspaces, you might use the workspace ID so that all members of the same workspace share the same templates. If a tenant identifier that doesn't exist yet is passed, Templatical will automatically create a new tenant for it.
API Response
The token endpoint returns:
{
"token": "eyJhbGciOiJIUzI1NiIs...",
"expires_at": 1709251200,
"project_id": "proj_abc123",
"tenant": "acme-corp"
}| Field | Type | Description |
|---|---|---|
token | string | Bearer access token for API requests |
expires_at | number | Unix timestamp (seconds) when the token expires. Tokens are valid for 10 minutes |
project_id | string | Your project identifier |
tenant | string | The resolved tenant for this session |
Your backend should forward this entire response to the browser as-is.
Backend Examples
app.post('https://your-app.com/api/token', async (req, res) => {
const response = await fetch('https://templatical.com/api/v1/auth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
client_id: process.env.TEMPLATICAL_CLIENT_ID,
client_secret: process.env.TEMPLATICAL_CLIENT_SECRET,
tenant: req.user.organizationId,
}),
});
const tokenData = await response.json();
res.json(tokenData);
});SDK Configuration
Once your token endpoint is ready, configure the SDK to use it:
import { init } from '@templatical/embedded';
const editor = await init({
container: '#email-editor',
auth: {
url: 'https://your-app.com/api/token',
},
});Custom Request Options
If your token endpoint requires specific HTTP methods, headers, or a request body, use the auth.requestOptions property:
const editor = await init({
container: '#email-editor',
auth: {
url: 'https://your-app.com/api/token',
requestOptions: {
method: 'POST', // 'GET' or 'POST' (default: 'POST')
headers: {
'X-Custom-Header': 'value',
},
body: {
workspace_id: 'ws_123', // Additional data to send
},
},
},
});| Option | Type | Default | Description |
|---|---|---|---|
method | 'GET' | 'POST' | 'POST' | HTTP method for the token request |
headers | Record<string, string> | {} | Additional headers to include |
body | Record<string, unknown> | — | JSON body to send (POST only) |
The SDK automatically includes Accept: application/json and Content-Type: application/json headers. It also sends cookies with the request (credentials: 'include'), so session-based authentication works out of the box.
Error Handling
Handle authentication errors using the onError callback on the config root:
const editor = await init({
container: '#email-editor',
auth: {
url: 'https://your-app.com/api/token',
},
onError: (error) => {
if (error.statusCode === 401) {
// Token endpoint returned unauthorized — redirect to login
window.location.href = '/login';
} else {
console.error('Editor error:', error.message);
}
},
});The error object is an instance of SdkError with these properties:
| Property | Type | Description |
|---|---|---|
message | string | Human-readable error message |
statusCode | number | undefined | HTTP status code (if from an API response) |
isNotFound | boolean | true when statusCode is 404 |
isUnauthorized | boolean | true when statusCode is 401 |
isServerError | boolean | true when statusCode is 500+ |
Token Refresh
The SDK manages token lifecycle automatically. Each token is valid for 10 minutes:
- On initialization, it fetches a token from your endpoint
- Before the token expires (60 seconds prior), it fetches a new one
- If an API request returns 401, it refreshes the token and retries the request once
- Concurrent refresh requests are deduplicated — only one network call is made
You do not need to implement any token refresh logic in your application.
Security Best Practices
- Never expose your Client Secret in frontend code. Always exchange credentials on your backend.
- Use HTTPS for your token endpoint and all communication.
- Scope tenants correctly. Ensure your token endpoint passes the correct tenant identifier for each request. Templates, media, and settings are isolated per tenant — using the wrong identifier will grant access to the wrong data.
Next Steps
- Configuration — All configuration options, callbacks, and instance methods