REST API Authentication and Endpoints

The complete reference for AcelleMail's REST API — all resources, all routes, the per-endpoint request/response shapes, and the scoping rules. Use this alongside the getting-started guide; this article is the lookup table.

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:

  • UI: Profile → API Token → Generate / Copy
  • API: POST /api/v1/user/login (unauthenticated):
    POST /api/v1/user/login
    email=you@example.com&password=Yours
    → 200 OK
    { "api_token": "..." }
    

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

8 commenti

3 commenti

  1. lucas.bernard.…
    can webhook secrets be rotated without downtime, or is there an overlap mechanism? we rotate everything quarterly.
    1. admin
      good question — and one that comes up often enough we should add an FAQ section. Short answer: yes for the common case; the exception is when you're running custom plugins that override the default behavior.
    2. admin (modificato)
      Suppression list import via CSV captures all opt-outs including preference-center ones if you exported with the right field set. The export filter defaults exclude some — check the 'include unsubscribed' checkbox on Mailchimp's export wizard.
    3. admin (modificato)
      We don't recommend that approach in production. It works in dev but has subtle race conditions under concurrent load. Stick with the documented pattern.
  2. ravi.kumar.del…
    What's the recommended retry policy for failed deliveries on the receiver side? We see ~1% transient failures and aren't sure if we should auto-retry or rely on AcelleMail's redelivery.
    1. admin
      We're aware of the silent-bail-out on deleted customers — there's an open issue for it. Workaround for now: monitor the campaign:rerun log for absence of expected log lines, alert when silent for > 20 min.
    2. admin (modificato)
      we tested this with up to 1M subscribers on a $40/mo VPS. Past that you start needing query optimization. Below that, the defaults are fine :)
  3. lequan.saigon
    Webhook signature verification is the kind of thing most docs hand-wave. The constant-time-compare warning is genuinely important — I've seen production codebases with == on signatures.

More in Developer Guide