What this is for#
AcelleMail ships a REST API that exposes nearly every operation the admin UI offers — create lists, add subscribers, send campaigns, trigger automations, read reports. If you've ever wished you could "do that thing in the admin, but from my own code", the API is the answer.
This guide is the 5-minute intro: where to find your API token, two ways to send it, the first call to make to confirm everything works, and the common failure modes. Once this works, jump to the REST API Authentication and Endpoints reference for the full surface area.
Step 1 — Get your API token#
Every AcelleMail account (admin OR customer) has an api_token stored in the database. Two ways to retrieve it:
Method A — From the UI (easiest)#
- Log in to AcelleMail
- Click your name (top-right) → Account / Profile
- Look for the API Token field (or Developer / API section depending on your version)
- Click "Generate token" if no token exists, or "Show" / "Copy" if one does
The token is a long random string like Y2x4cTBxbGZ6MDAwMDAxazB0cWxqYmJ4dQ. Treat it like a password — anyone with this token can act as you.
Method B — Via the login endpoint (for headless / scripted setups)#
curl -X POST https://mail.example.com/api/v1/user/login \
-H "Accept: application/json" \
-d 'email=you@example.com' \
-d 'password=YourPassword'
# Response:
# {"api_token": "Y2x4cTBxbGZ6MDAwMDAxazB0cWxqYmJ4dQ"}
Store the returned api_token in your code's secrets manager / .env file. Never commit it to version control.
Step 2 — Two ways to send the token#
Both are equivalent; pick whichever fits your HTTP client better.
Method A — Authorization: Bearer header (preferred)
curl -H "Authorization: Bearer Y2x4cTBxbGZ6MDAwMDAxazB0cWxqYmJ4dQ" \
-H "Accept: application/json" \
https://mail.example.com/api/v1/me
Standard OAuth-style header. Works in every HTTP client. Doesn't show up in server access logs (where the URL does).
Method B — ?api_token=XXX query parameter#
curl "https://mail.example.com/api/v1/me?api_token=Y2x4cTBxbGZ6MDAwMDAxazB0cWxqYmJ4dQ" \
-H "Accept: application/json"
Easier for quick CLI testing. Avoid in production — query strings get logged in access logs, browser history, referrers. Use the header.
Step 3 — Your first call#
The /api/v1/me endpoint returns the authenticated user's profile. It's the standard "is my auth working?" check:
curl -H "Authorization: Bearer YOUR_TOKEN" \
-H "Accept: application/json" \
https://mail.example.com/api/v1/me
Expected response (HTTP 200):
{
"id": 1,
"uid": "Y2x4cTBxbGZ6MDAwMDAxazB0cWxqYmJ4dQ",
"email": "you@example.com",
"first_name": "Your",
"last_name": "Name",
"created_at": "2024-01-15T10:32:11.000000Z",
"customer": {
"id": 5,
"uid": "...",
"company": "Acme Corp"
}
}
If you see your own data, the API is working and your auth is correct. Move on to the endpoint reference.
Step 4 — Client snippets#
Once auth works in curl, the same pattern translates to any language. Here are tested snippets:
PHP (Guzzle)#
$client = new \GuzzleHttp\Client([
'base_uri' => 'https://mail.example.com/api/v1/',
'headers' => [
'Authorization' => 'Bearer ' . getenv('ACELLEMAIL_TOKEN'),
'Accept' => 'application/json',
],
]);
$response = $client->get('me');
$me = json_decode($response->getBody(), true);
echo $me['email'];
Python (requests)#
import os
import requests
BASE = 'https://mail.example.com/api/v1'
HEADERS = {
'Authorization': f"Bearer {os.environ['ACELLEMAIL_TOKEN']}",
'Accept': 'application/json',
}
r = requests.get(f'{BASE}/me', headers=HEADERS)
r.raise_for_status()
print(r.json()['email'])
Node.js (fetch)#
const BASE = 'https://mail.example.com/api/v1';
const token = process.env.ACELLEMAIL_TOKEN;
const res = await fetch(`${BASE}/me`, {
headers: {
'Authorization': `Bearer ${token}`,
'Accept': 'application/json',
},
});
const me = await res.json();
console.log(me.email);
Go (net/http)#
package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
)
func main() {
req, _ := http.NewRequest("GET", "https://mail.example.com/api/v1/me", nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("ACELLEMAIL_TOKEN"))
req.Header.Set("Accept", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
var me struct{ Email string }
json.NewDecoder(resp.Body).Decode(&me)
fmt.Println(me.Email)
}
Step 5 — Try something useful#
Now that auth works, fetch your subscriber lists:
curl -H "Authorization: Bearer YOUR_TOKEN" \
-H "Accept: application/json" \
https://mail.example.com/api/v1/lists
Response shape:
{
"data": [
{ "uid": "abc123", "name": "Newsletter", "subscribers_count": 5230, ... },
{ "uid": "def456", "name": "Customers", "subscribers_count": 1842, ... }
],
"current_page": 1,
"per_page": 20,
"total": 2
}
The uid field is what you'll use for subsequent calls (e.g. GET /api/v1/lists/abc123/subscribers). See the endpoint reference for what each resource looks like.
Common issues#
| What you see |
Likely cause |
Fix |
401 Unauthenticated |
Wrong / expired / missing token |
Re-fetch from UI; verify you're hitting the right base URL (/api/v1/ not /api/) |
404 Not Found on a known endpoint |
Wrong base URL — used /api/me instead of /api/v1/me |
All endpoints are versioned under /v1/ |
415 Unsupported Media Type |
Missing Accept: application/json header |
Always include this header on every request |
422 Unprocessable Entity |
Validation error on request body |
Response body lists exactly which fields failed; fix and retry |
429 Too Many Requests |
Rate-limit hit (if your install has rate limiting) |
Back off; check Retry-After header for wait time |
500 Internal Server Error |
Server-side bug |
Check /var/www/acellemail/storage/logs/laravel.log for the stack trace |
Empty data: [] on /lists |
Token belongs to a different customer with no lists |
Verify which customer the token is for via /me → customer.id; lists are scoped per customer |
| Different shape than docs say |
API response shape varies slightly across major Acelle versions |
Print the full JSON in development; rely on field presence checks |
cURL: (60) SSL certificate problem |
Self-signed cert on staging install |
curl -k ... for testing; fix the cert before production |
| Login endpoint returns "Invalid credentials" |
Email/password wrong, OR 2FA enabled |
If 2FA is on, you can't get a token via login endpoint — use the UI method (Step 1A) |
Rate limits#
By default AcelleMail doesn't impose API rate limits — it trusts authenticated users. If you're hitting the API hard (1000+ calls/sec from a script), consider adding throttling middleware via the extending source code guide.
For public endpoints (signup forms via /api/v1/public/subscribers), DO add throttling — it's the typical spam-bot target.
What's authenticated as what#
A token belongs to one user, and inherits that user's permissions:
- Admin user token: can access
/customers, /subscriptions, /sending_servers admin endpoints + all customer endpoints
- Customer user token: can only access endpoints scoped to their customer record (their lists, their campaigns, their automations)
This matters for SaaS operators — never give an end customer an admin token; generate them their own.
Versioning#
Current version is v1. AcelleMail has held this API stable across multiple major Acelle releases (4.x → 5.x). Breaking changes are rare; additive changes (new endpoints, new fields) are common — always parse responses defensively (response.field ?? default).
If a future /v2/ ships, the announcement will be in acellemail-updates with a migration period for v1.
FAQ#
Can I use OAuth instead of token? Not natively in current AcelleMail. The token is functionally equivalent for machine-to-machine use cases.
Is there a Postman collection? Not officially shipped. Build your own by importing the OpenAPI spec — see the endpoint reference for the resource layout.
Can a token be revoked? Yes — generate a new one in the UI (it replaces the old one). The old token immediately becomes invalid.
How do I expire tokens after N days? Not built-in; add a custom Laravel scheduled task that nulls users.api_token for inactive accounts.
Webhooks? Yes, separate from the API — see Webhook Events Reference for outbound events (new_subscription, change_plan, etc.).
Is there a Python / JS SDK? Not officially. The API surface is small enough that a hand-rolled client per language is straightforward — see Step 4 snippets.
Can I use this from a browser (CORS)? AcelleMail doesn't ship CORS headers by default — your /api/v1/* calls from a different origin will be blocked. Either proxy through your own backend (recommended) or add CORS headers in nginx for the API routes (see post-install hardening).
Related articles#