Server API
Send users and events to Atlis from your backend, CRM or automation tool.
Available on: Growth, Scale or an active trial
Use the server API when data lives on your server, in a CRM, or in a tool like Zapier. It works on its own or alongside the tracking snippet.
1. Get your API key
Go to Settings → Integrations → Server API & CRM and click Generate API key. The key starts with atlis_sk_ and is shown only once, so store it somewhere safe. Regenerate key replaces it, and the old key stops working immediately.
Keep the key on your server or in your CRM. Never put it in browser code.
2. Create or update a user
POST /api/v1/identify creates the user if they're new, or updates them. Send it before any events for that user.
curl -X POST https://theatlis.com/api/v1/identify \
-H "Authorization: Bearer atlis_sk_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"userId": "12345",
"email": "jane@company.com",
"name": "Jane Smith",
"plan": "pro",
"isActivated": false,
"trialExpiresAt": "2026-12-01T00:00:00Z",
"signedUpAt": "2026-09-01T10:00:00Z",
"properties": { "company": "Acme" }
}'| Field | Type | Notes |
|---|---|---|
| userId | string, required | Your permanent ID for this person. Must not contain /. |
| string | Needed to send them emails. | |
| name | string | Used as {{displayName}} in emails. |
| plan | string | For example free, trial, pro, annual. |
| isActivated | true / false | Whether they reached your activation milestone. |
| trialExpiresAt | ISO date or milliseconds | Trial end date. Powers Trial expiring in 3 days. |
| signedUpAt | ISO date or milliseconds | Real signup date, used only when the user is first created. Future dates and dd/mm/yyyy formats are ignored. |
| lastActiveAt | ISO date or milliseconds | When they were last active in your product. Only moves forward, never back. |
| sourceTag | string | Where the user came from, for example Website or Google Ads. |
| firstTouch / lastTouch | object | Optional traffic details for the first and latest visit: source, medium, campaign, content, term, referrer, landing_page. The first touch is kept once set, and it takes priority over sourceTag. |
| properties | object | Any extra fields. Empty values are ignored, so they never erase existing data. |
| eventName | string | Optional: also record an event in the same call (see below). |
| messageId | string | Optional: a unique ID for that event, so a retry doesn't send twice. |
A successful call returns 200 {"success": true}.
3. Send an event
POST /api/v1/events records something the user did. If a workflow for that trigger is switched on, its email sends.
curl -X POST https://theatlis.com/api/v1/events \
-H "Authorization: Bearer atlis_sk_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"userId": "12345",
"eventName": "trial_started",
"messageId": "trial_started_12345",
"properties": { "plan": "pro" }
}'| Field | Type | Notes |
|---|---|---|
| userId | string, required | Must already exist. Identify the user first. |
| eventName | string, required | A trigger name such as trial_started, or your own custom name. |
| properties | object | Optional details saved with the event. |
| messageId | string | Optional unique ID. Sending the same messageId again is ignored, so retries are safe. |
A successful call returns 200 {"ok": true}. The event time is the moment Atlis receives it. Events sent to this endpoint also count as activity, so they update the user's last active time.
Authentication
| Endpoint | Accepted ways to send the key |
|---|---|
| /api/v1/identify | Authorization: Bearer <key> header (recommended), an x-api-key header, or an apiKey field in the JSON body |
| /api/v1/events | Same as identify: Authorization: Bearer <key> header, an x-api-key header, or an apiKey field in the JSON body |
| /api/v1/inbound | Same as identify, plus ?key=<key> in the URL or the key as the Basic auth password |
Code examples
// Node.js 18+ (uses the built-in fetch)
const ATLIS_KEY = process.env.ATLIS_API_KEY;
async function atlis(path, body) {
const res = await fetch("https://theatlis.com/api/v1/" + path, {
method: "POST",
headers: {
"Authorization": "Bearer " + ATLIS_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
if (!res.ok) throw new Error("Atlis " + res.status + ": " + (await res.text()));
return res.json();
}
await atlis("identify", { userId: "12345", email: "jane@company.com", name: "Jane Smith" });
await atlis("events", { userId: "12345", eventName: "first_project_created" });Responses and errors
| Status | Error | What to do |
|---|---|---|
| 400 | Invalid JSON (identify) | Send a valid JSON body with Content-Type: application/json. |
| 400 | Missing required field: userId / missing_user_id | Add a userId. |
| 400 | Invalid userId / invalid_user_id | Remove any / from the ID. |
| 400 | missing_event_name | Add an eventName (events endpoint). |
| 400 | Invalid messageId / invalid_message_id | Use a plain text messageId without /. |
| 400 | invalid_json (events) | Send a valid JSON body with Content-Type: application/json. |
| 400 | inbound_mapping_not_set / missing_user_id (inbound) | Save the field mapping in Settings → Integrations → Server API & CRM, and check the mapped User ID or Email field isn't empty in the payload. |
| 401 | invalid_api_key or Unauthorized | Check the key is current and sent in one of the supported ways. |
| 403 | plan_limit_exceeded | Your plan doesn't include the API, or you've reached your tracked-user limit. |
| 403 | plan_upgrade_required | The events endpoint needs Growth, Scale or an active trial. |
| 404 | unknown_user | Call /api/v1/identify for this user first. |
| 429 | rate_limited | Slow down. The events endpoint accepts up to 100 requests per minute per workspace. |
| 500 | internal_server_error / Internal server error | Retry later. |
Time-based emails for API users
- The Inactive 7/14 days, Testimonial request and Not activated in 48h workflows depend on activity. Atlis skips them for API users until it has real activity for them.
- Activity comes from
/api/v1/eventscalls, alastActiveAtvalue in identify, or the tracking snippet. Events sent inside an identify call (eventName) don't count as activity. - For CRM leads with no product activity, you can still send the Not activated email: see CRM status mapping.
- Trial expiring in 3 days works for API users as long as you send
trialExpiresAt.
More in Server API & CRM
Try it in your own workspace
Start with a 14-day trial with Growth features. No credit card needed.
Start free