What this is for#
The complete reference for AcelleMail's REST API. Use this alongside the Getting Started guide — this article is the lookup table.
Base URL: https://mail.example.com/api/v1/
Auth: token (see below) — every authenticated route requires it
Format: request Content-Type: application/json (POST/PATCH/PUT) + Accept: application/json on every request
Authentication#
AcelleMail uses Laravel's auth:api driver — a simple bearer-token scheme.
Get a token#
Two methods:
Send the token#
Either is valid:
| Method |
Format |
Use when |
| Header (preferred) |
Authorization: Bearer <token> |
Production. Doesn't leak to access logs. |
| Query param |
?api_token=<token> |
Quick CLI tests. Avoid in production. |
Scoping rules#
A token is tied to one user, and inherits their scope:
- Customer user token → can read/write only resources owned by that customer (their lists, campaigns, subscribers, automations)
- Admin user token → can additionally hit admin-only endpoints (
/customers, /subscriptions, /sending_servers create/edit)
You cannot impersonate another user. To act as a customer, log in as that customer and use their token.
Conventions#
- UIDs vs IDs: every resource has both an integer
id (DB primary key) and a string uid (URL-safe, opaque). Routes use uid (e.g. /lists/{uid}/subscribers). Responses include both.
- Pagination: list endpoints return paginated responses with
data, current_page, per_page, total, last_page. Use ?page=2&per_page=50 to navigate (max per_page typically 100).
- Filtering: most list endpoints support
?filter[field]=value style filters; specific allowed filters vary per endpoint.
- Errors: standard Laravel error responses —
422 for validation, 404 for missing, 401 for auth, 403 for forbidden, 500 for server error. Body includes message + (on 422) errors[].
- Timestamps: ISO 8601 UTC strings (
2026-05-16T14:32:11.000000Z).
Resources#
Public (unauthenticated)#
Subscribers — public signup form#
POST /api/v1/public/subscribers
Content-Type: application/json
{
"MAIL_LIST_UID": "abc123",
"EMAIL": "alice@example.com",
"FIRST_NAME": "Alice",
"LAST_NAME": "Doe",
...custom fields per the list schema...
}
→ 200 OK
{ "uid": "subUID", "status": "subscribed" }
The only public endpoint. Use it for signup forms on your marketing site — no token needed.
Add rate limiting at the nginx level to defend against spam bots (see post-install hardening).
Authentication + identity#
| Endpoint |
Method |
Description |
/me |
GET |
Current authenticated user + their customer |
/whoami |
GET |
Lighter-weight identity check (id + email only) |
/user/login |
POST |
Exchange email+password for an api_token (unauthenticated) |
/user/info |
GET |
Same as /me (legacy alias) |
/dashboard |
GET |
Customer's dashboard summary stats |
Lists#
| Endpoint |
Method |
Description |
/lists |
GET |
List all subscriber lists owned by current customer |
/lists |
POST |
Create a new subscriber list |
/lists/{uid} |
GET |
Get a specific list |
/lists/{uid} |
PUT/PATCH |
Update list fields |
/lists/{uid} |
DELETE |
Delete a list (with all its subscribers) |
/lists/{uid}/add-field |
POST |
Add a custom field to the list |
Create example:
POST /api/v1/lists
{
"name": "Newsletter Q2",
"from_email": "news@example.com",
"from_name": "Example Team",
"default_subject": "Update from Example",
"contact": {
"company": "Example Co",
"address_1": "123 Main St",
"city": "San Francisco",
"country_id": 234,
"url": "https://example.com"
}
}
→ 201 Created
{ "uid": "newListUID", "name": "Newsletter Q2", ... }
Subscribers#
| Endpoint |
Method |
Description |
/subscribers |
GET |
List subscribers (paginated) — filter via ?list_uid= |
/subscribers |
POST |
Add a new subscriber to a list |
/subscribers/{id} |
GET |
Get a subscriber by id |
/subscribers/{id} |
PATCH |
Update subscriber fields |
/subscribers/{id} |
DELETE |
Delete a subscriber |
/subscribers/email/{email} |
GET |
Find subscriber by email address |
/lists/{list_uid}/subscribers/{id}/subscribe |
PATCH |
Mark subscribed |
/lists/{list_uid}/subscribers/{id}/unsubscribe |
PATCH |
Mark unsubscribed |
/lists/{list_uid}/subscribers/email/{email}/unsubscribe |
PATCH |
Unsubscribe by email (no id lookup needed) |
/subscribers/{id}/add-tag |
POST |
Tag a subscriber |
/subscribers/{id}/remove-tag |
POST |
Untag a subscriber |
Add subscriber:
POST /api/v1/subscribers
{
"MAIL_LIST_UID": "abc123",
"EMAIL": "alice@example.com",
"FIRST_NAME": "Alice"
}
→ 201 Created
{ "uid": "subUID", "email": "alice@example.com", "status": "subscribed" }
Campaigns#
| Endpoint |
Method |
Description |
/campaigns |
GET |
List campaigns (paginated) |
/campaigns |
POST |
Create a new campaign |
/campaigns/{uid} |
GET |
Get campaign details + stats |
/campaigns/{uid} |
PATCH |
Update campaign fields (only allowed before "running") |
/campaigns/{uid} |
DELETE |
Delete a campaign |
/campaigns/{uid}/run |
POST |
Start sending the campaign |
/campaigns/{uid}/pause |
POST |
Pause an in-progress send |
/campaigns/{uid}/resume |
POST |
Resume a paused campaign |
/campaigns/{uid}/tracking-log/download |
GET |
Download tracking event log (CSV) |
/campaigns/{uid}/open-log/download |
GET |
Download open events (CSV) |
/campaigns/{uid}/click-log/download |
GET |
Download click events (CSV) |
/campaigns/{uid}/bounce-log/download |
GET |
Download bounce events (CSV) |
/campaigns/{uid}/feedback-log/download |
GET |
Download spam-complaint events (CSV) |
/campaigns/{uid}/unsubscribe-log/download |
GET |
Download unsubscribe events (CSV) |
Automations#
| Endpoint |
Method |
Description |
/automations |
GET |
List automations |
/automations/{uid}/api/call |
POST |
Trigger an "API trigger" inside an automation (start a subscriber down the flow) |
/automations/{uid}/execute |
POST |
Manually fire the automation for a given subscriber |
The api/call endpoint is what powers the API trigger feature — your external app POSTs to this URL to begin an automation flow for a specific subscriber.
Sending servers#
| Endpoint |
Method |
Description |
/sending_servers |
GET |
List sending servers (admin only) |
/sending_servers |
POST |
Create sending server (admin only) |
/sending_servers/{uid} |
GET / PATCH / DELETE |
Standard resource ops |
Customers (admin only)#
| Endpoint |
Method |
Description |
/customers |
GET |
List all customers |
/customers |
POST |
Create a new customer |
/customers/{uid} |
GET / PATCH / DELETE |
Standard resource ops |
/customers/by-email/{email} |
GET |
Find customer by email |
/customers/{uid}/disable |
PATCH |
Suspend the customer |
/customers/{uid}/enable |
PATCH |
Reactivate |
/customers/{uid}/assign-plan/{plan_uid} |
POST |
Assign a plan |
/customers/{uid}/change-plan/{plan_uid} |
POST |
Change current plan |
/customers/{uid}/subscription/update |
POST |
Update subscription metadata |
/login-token |
GET/POST |
Generate a temporary login URL for impersonation (admin debugging) |
Subscriptions (admin only)#
| Endpoint |
Method |
Description |
/subscriptions |
GET / POST |
List / create subscription records |
/subscriptions/{uid} |
GET / PATCH / DELETE |
Standard resource ops |
Notifications (sending-provider webhook inbound)#
These endpoints receive webhooks from the sending provider (SES SNS, Mailgun, SendGrid event hooks) — you don't typically call them directly:
| Endpoint |
Method |
Description |
/notification/bounce |
POST |
Sending provider posts bounce events here |
/notification/feedback |
POST |
Sending provider posts complaint events here |
File upload + management#
| Endpoint |
Method |
Description |
/file/upload |
POST |
Upload a file (for use in email templates / campaigns) |
/filemanager/list |
GET |
List uploaded files |
/filemanager/upload |
POST |
Upload to file manager |
/filemanager/delete |
DELETE |
Delete uploaded file |
/filemanager/rename |
PATCH |
Rename |
/filemanager/view |
GET |
Read file contents |
System / admin#
| Endpoint |
Method |
Description |
/plugins/install |
POST |
Install a plugin from zip (admin only) |
/upgrade/run |
POST |
Run an upgrade from a URL |
/upgrade/run-file |
POST |
Run an upgrade from an uploaded .bin patch file |
/upgrade/finalize |
POST |
Run post-upgrade tasks |
/license/refresh |
POST |
Re-check the CodeCanyon license |
Common patterns#
Paginated list#
# Get page 2, 50 per page
curl -H "Authorization: Bearer $TOKEN" \
"https://mail.example.com/api/v1/subscribers?page=2&per_page=50&filter[list_uid]=abc123"
Create then add many subscribers#
# 1. Create the list
LIST_UID=$(curl -s -X POST ... /api/v1/lists -d '...' | jq -r .uid)
# 2. Bulk-add subscribers (loop or batch via the import flow for > 1000)
for email in $(cat emails.txt); do
curl -X POST ... /api/v1/subscribers \
-d "MAIL_LIST_UID=$LIST_UID&EMAIL=$email"
done
For > 1000 subscribers, use the list-import flow — much faster than per-subscriber POST.
Send a one-off campaign via API#
# 1. Create the campaign
CAMP_UID=$(curl -s -X POST ... /api/v1/campaigns -d '{
"name": "Q2 Newsletter",
"type": "regular",
"subject": "Our June updates",
"from_email": "news@example.com",
"from_name": "Example",
"html_content": "<h1>Hi {FIRST_NAME}</h1>...",
"mail_list_uid": "abc123",
"sign_dkim": true,
"track_open": true,
"track_click": true
}' | jq -r .uid)
# 2. Start sending
curl -X POST -H "Authorization: Bearer $TOKEN" \
"https://mail.example.com/api/v1/campaigns/$CAMP_UID/run"
Trigger an automation#
# Customer X just bought a product; start them on the post-purchase flow
curl -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"EMAIL": "buyer@example.com",
"FIRST_NAME": "Buyer",
"product_name": "Widget Pro",
"purchase_amount": 99.00
}' \
https://mail.example.com/api/v1/automations/AUTOMATION_UID/api/call
The custom fields (product_name, purchase_amount) are available as merge tags inside the automation's emails.
Error reference#
| HTTP code |
Meaning |
Common cause |
200 |
OK |
Success |
201 |
Created |
Resource created (POST) |
204 |
No Content |
Success with no response body |
400 |
Bad Request |
Malformed JSON, missing required header |
401 |
Unauthenticated |
Missing/expired token |
403 |
Forbidden |
Token valid but lacks permission (e.g. customer trying admin endpoint) |
404 |
Not Found |
UID/email not found, or endpoint typo |
415 |
Unsupported Media Type |
Missing Accept: application/json header |
422 |
Unprocessable Entity |
Validation failed; response includes errors[] per-field |
429 |
Too Many Requests |
Rate limit hit (if configured) |
500 |
Internal Server Error |
Server bug; check storage/logs/laravel.log |
A typical 422 looks like:
{
"message": "The given data was invalid.",
"errors": {
"EMAIL": ["The EMAIL field is required."],
"MAIL_LIST_UID": ["The selected MAIL_LIST_UID is invalid."]
}
}
Tips for production use#
- Log every API call's request id (Laravel emits one per request in the response headers) for traceable debugging
- Set request timeout in your client to ≥ 30 seconds — bulk operations (large list creation, campaign run) can take that long
- Retry on 5xx with exponential backoff; don't retry on 4xx (it'll just fail again)
- Never log the token itself — sanitize before logging headers
- Rotate the token quarterly by regenerating in the UI
FAQ#
Is the API stable across Acelle versions? v1 has been stable across multiple major releases. Additive changes (new fields, new endpoints) are common; breaking changes are rare and pre-announced in acellemail-updates.
Is there an OpenAPI / Swagger spec? Not officially shipped. Build by introspecting routes/api.php and generating from controller signatures.
Does the API support GraphQL? No — REST only.
Can the API run a campaign for me without an existing list? No — campaigns must reference a mail_list_uid that already exists. Create the list first.
What's the difference between id and uid? id is the DB primary key (integer); uid is the URL-safe string identifier. Always use uid in routes; you'll see both in responses.
Webhooks? Different system from the API. See Webhook Events Reference for outbound events your app can subscribe to.
Related articles#