Core

Lists API

8 endpoints under https://api.mailneo.co/api/v2. Every request carries your key in the X-API-Key header. The scopes used here: lists:read, lists:write, subscribers:read. Each endpoint below states the one it requires. Start with the guide →

Lists
listLists

List subscriber lists

Cursor-paginated. Pass updated_since with the synced_through value from your previous run to fetch only what changed.

subscriber_count is not on this response — it costs one COUNT(*) per list and would be 100 of them on a full page. Retrieve a single list to get it, or page GET /lists/{listId}/subscribers.

https://api.mailneo.co/api/v2
GET/listslists:read

Example request

curl "https://api.mailneo.co/api/v2/lists?limit=25&order=desc&sort=updated_at" \
  -H "X-API-Key: $MAILNEO_API_KEY"
Authorization
Required scope
lists:read. A key without it is refused before the endpoint runs.
Credential
X-API-Key: mk_live_...Authorization: Bearer mk_live_...
Query parameters8
NameTypeRequiredNotes
limitintegerNodefault 25 · 1–100
cursorstringNomax 256 chars
order"asc" | "desc"Nodefault "desc"
updated_sincestring · date-timeNoISO 8601
sort"updated_at" | "created_at"Nodefault "updated_at"
type"PUBLIC" | "PRIVATE" | "TEMPORARY"No
optin"SINGLE" | "DOUBLE"No
namestringNomax 200 chars
Response · 200

Success

data[]Each element of the array.

FieldTypeAlways presentNotes
object"list"Yes
idstringYes
namestringYes
descriptionstringYes
typestringYes
optinstringYes
tagsstring[]Yes
created_atstringYes
updated_atstringYes

meta

FieldTypeAlways presentNotes
request_idstringYes
has_morebooleanYes
next_cursorstring · nullableYes
synced_throughstringNo
Listswrite
createList

Create a list

Creates one empty list. Names are NOT required to be unique — the app does not enforce it either, so two lists may share a name and are told apart by id.

optin decides how a subscription to this list starts: on a SINGLE list a new member is CONFIRMED immediately, on a DOUBLE list they are UNCONFIRMED until they click a confirmation link. Choose it now — changing it later does not revisit the members already on the list.

https://api.mailneo.co/api/v2
POST/listslists:write

Example request

curl -X POST "https://api.mailneo.co/api/v2/lists" \
  -H "X-API-Key: $MAILNEO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "<string>"
  }'
Authorization
Required scope
lists:write. A key without it is refused before the endpoint runs.
Credential
X-API-Key: mk_live_...Authorization: Bearer mk_live_...
Idempotency
Idempotency-Key is optional.
Query parameters1
NameTypeRequiredNotes
dry_runbooleanNoReport what this call WOULD do and commit nothing. Returns data: null and a meta.impact block. Requires the resource's :read scope in addition to the scope this endpoint declares. Must be exactly true or false.
Header parameters1
NameTypeRequiredNotes
Idempotency-KeystringNoA caller-chosen unique string, at most 255 characters. Retrying with the same key replays the first response instead of repeating the work; the replay carries Idempotent-Replayed: true. Reusing a key with a different body is a 409 idempotency_key_reused. Keys are remembered for 24 hours. · max 255 chars
Request body

Body

FieldTypeAlways presentNotes
namestringYesmax 200 chars
descriptionstringNomax 500 chars
type"PUBLIC" | "PRIVATE" | "TEMPORARY"No
optin"SINGLE" | "DOUBLE"No
tagsstring[]No
Response · 200

Success

data

FieldTypeAlways presentNotes
dataobject | object · nullableYes

meta

FieldTypeAlways presentNotes
request_idstringYes
dry_runbooleanYes
impactobjectNo
impact.object"impact"Yes
impact.would_createintegerYes
impact.would_updateintegerYes
impact.would_deleteintegerYes
impact.affected_countintegerYes
impact.affected_idsstring[]Yes
impact.quotaobject · nullableYes
impact.quota.kindstringYes
impact.quota.would_consumeintegerYes
impact.quota.limitinteger · nullableYes
impact.quota.currentintegerYes
Response · 201

Created

data

FieldTypeAlways presentNotes
dataobject | object · nullableYes

meta

FieldTypeAlways presentNotes
request_idstringYes
dry_runbooleanYes
impactobjectNo
impact.object"impact"Yes
impact.would_createintegerYes
impact.would_updateintegerYes
impact.would_deleteintegerYes
impact.affected_countintegerYes
impact.affected_idsstring[]Yes
impact.quotaobject · nullableYes
impact.quota.kindstringYes
impact.quota.would_consumeintegerYes
impact.quota.limitinteger · nullableYes
impact.quota.currentintegerYes
Lists
getList

Retrieve a list

Includes subscriber_count: every subscriber who is a member of this list, whatever their status and whatever their per-list subscription state.

https://api.mailneo.co/api/v2
GET/lists/{listId}lists:read

Example request

curl "https://api.mailneo.co/api/v2/lists/{listId}" \
  -H "X-API-Key: $MAILNEO_API_KEY"
Authorization
Required scope
lists:read. A key without it is refused before the endpoint runs.
Credential
X-API-Key: mk_live_...Authorization: Bearer mk_live_...
Path parameters1
NameTypeRequiredNotes
listIdstringYesmax 64 chars
Response · 200

Success

data

FieldTypeAlways presentNotes
object"list"Yes
idstringYes
namestringYes
descriptionstringYes
typestringYes
optinstringYes
tagsstring[]Yes
created_atstringYes
updated_atstringYes
subscriber_countintegerYes

meta

FieldTypeAlways presentNotes
request_idstringYes
Listswrite
updateList

Update a list

Merge-patch: fields you omit are left alone. Nothing on a list is nullable, so sending null for a field is a 400 — clear description with "" and tags with [].

Changing optin applies to FUTURE members only. Moving a list from SINGLE to DOUBLE does not retroactively unconfirm anyone already on it, and moving it the other way does not confirm anyone.

https://api.mailneo.co/api/v2
PATCH/lists/{listId}lists:write

Example request

curl -X PATCH "https://api.mailneo.co/api/v2/lists/{listId}" \
  -H "X-API-Key: $MAILNEO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "<string>",
    "description": "<string>",
    "type": "PUBLIC",
    "optin": "SINGLE",
    "tags": [
      "<string>"
    ]
  }'
Authorization
Required scope
lists:write. A key without it is refused before the endpoint runs.
Credential
X-API-Key: mk_live_...Authorization: Bearer mk_live_...
Idempotency
Idempotency-Key is optional.
Path parameters1
NameTypeRequiredNotes
listIdstringYesmax 64 chars
Query parameters1
NameTypeRequiredNotes
dry_runbooleanNoReport what this call WOULD do and commit nothing. Returns data: null and a meta.impact block. Requires the resource's :read scope in addition to the scope this endpoint declares. Must be exactly true or false.
Header parameters1
NameTypeRequiredNotes
Idempotency-KeystringNoA caller-chosen unique string, at most 255 characters. Retrying with the same key replays the first response instead of repeating the work; the replay carries Idempotent-Replayed: true. Reusing a key with a different body is a 409 idempotency_key_reused. Keys are remembered for 24 hours. · max 255 chars
Request body

Body

FieldTypeAlways presentNotes
namestringNomax 200 chars
descriptionstringNomax 500 chars
type"PUBLIC" | "PRIVATE" | "TEMPORARY"No
optin"SINGLE" | "DOUBLE"No
tagsstring[]No
Response · 200

Success

data

FieldTypeAlways presentNotes
dataobject | object · nullableYes

meta

FieldTypeAlways presentNotes
request_idstringYes
dry_runbooleanYes
impactobjectNo
impact.object"impact"Yes
impact.would_createintegerYes
impact.would_updateintegerYes
impact.would_deleteintegerYes
impact.affected_countintegerYes
impact.affected_idsstring[]Yes
impact.quotaobject · nullableYes
impact.quota.kindstringYes
impact.quota.would_consumeintegerYes
impact.quota.limitinteger · nullableYes
impact.quota.currentintegerYes
Listswrite
deleteList

Delete a list

Permanent, and there is no undo — call with ?dry_run=true first.

The subscribers on the list are NOT deleted; they keep every other list they are on. What is removed is their membership of THIS list, including any record that someone had unsubscribed from it — so re-creating the list and re-adding the same people will mail addresses that had opted out of it. Prefer leaving an unused list in place.

Two references are silently detached rather than blocked. A newsletter campaign that targeted this list keeps its targeting row with the list unset (the list NAME is retained on it for reporting), and any integration configured to sync into this list is left with no destination and will stop delivering into it. Check both before deleting a list something else feeds.

https://api.mailneo.co/api/v2
DELETE/lists/{listId}lists:write

Example request

curl -X DELETE "https://api.mailneo.co/api/v2/lists/{listId}" \
  -H "X-API-Key: $MAILNEO_API_KEY"
Authorization
Required scope
lists:write. A key without it is refused before the endpoint runs.
Credential
X-API-Key: mk_live_...Authorization: Bearer mk_live_...
Path parameters1
NameTypeRequiredNotes
listIdstringYesmax 64 chars
Query parameters1
NameTypeRequiredNotes
dry_runbooleanNoReport what this call WOULD do and commit nothing. Returns data: null and a meta.impact block. Requires the resource's :read scope in addition to the scope this endpoint declares. Must be exactly true or false.
Response · 200

Success

dataMay be null.

FieldTypeAlways presentNotes
object"list"Yes
idstringYes
deletedtrueYes

meta

FieldTypeAlways presentNotes
request_idstringYes
dry_runbooleanYes
impactobjectNo
impact.object"impact"Yes
impact.would_createintegerYes
impact.would_updateintegerYes
impact.would_deleteintegerYes
impact.affected_countintegerYes
impact.affected_idsstring[]Yes
impact.quotaobject · nullableYes
impact.quota.kindstringYes
impact.quota.would_consumeintegerYes
impact.quota.limitinteger · nullableYes
impact.quota.currentintegerYes
Lists
listListSubscribers

List the subscribers on a list

The members of one list, in the same subscriber shape and with the same filters as GET /subscribers. Cursor-paginated, and updated_since tracks the SUBSCRIBER's updated_at, not the membership row's — so a subscriber added to this list without being edited will not appear in an incremental poll. Do a full walk when you need membership changes.

Requires subscribers:read, not lists:read: this returns people, not lists. It is exactly equivalent to GET /subscribers?list_id=, which is gated the same way.

The membership record itself is never returned. It holds a double-opt-in confirmation token, which is a bearer secret.

https://api.mailneo.co/api/v2
GET/lists/{listId}/subscriberssubscribers:read

Example request

curl "https://api.mailneo.co/api/v2/lists/{listId}/subscribers?limit=25&order=desc&sort=updated_at" \
  -H "X-API-Key: $MAILNEO_API_KEY"
Authorization
Required scope
subscribers:read. A key without it is refused before the endpoint runs.
Credential
X-API-Key: mk_live_...Authorization: Bearer mk_live_...
Path parameters1
NameTypeRequiredNotes
listIdstringYesmax 64 chars
Query parameters9
NameTypeRequiredNotes
limitintegerNodefault 25 · 1–100
cursorstringNomax 256 chars
order"asc" | "desc"Nodefault "desc"
updated_sincestring · date-timeNoISO 8601
sort"updated_at" | "created_at"Nodefault "updated_at"
status"ENABLED" | "DISABLED" | "BLOCKLISTED"No
emailstringNomax 320 chars
sourcestringNomax 200 chars
tagsstringNomax 1000 chars
Response · 200

Success

data[]Each element of the array.

FieldTypeAlways presentNotes
object"subscriber"Yes
idstringYes
emailstringYes
namestringYes
first_namestring · nullableYes
last_namestring · nullableYes
phonestring · nullableYes
companystring · nullableYes
statusstringYes
tagsstring[]Yes
sourcestring · nullableYes
custom_fieldsobjectYesopen keys
last_contacted_atstring · nullableYes
created_atstringYes
updated_atstringYes

meta

FieldTypeAlways presentNotes
request_idstringYes
has_morebooleanYes
next_cursorstring · nullableYes
synced_throughstringNo
Listswrite
addListSubscribers

Add subscribers to a list

Attaches subscribers that already exist in your team to this list, up to 100 per call. It never CREATES a subscriber — that is subscribers:write — so an id with no subscriber behind it comes back under not_found rather than being minted.

Partial success is the normal outcome and the response says exactly which ids landed where: added, already_member (left alone, so re-posting the same batch is safe), unsubscribed and not_found.

Someone who previously unsubscribed from this list is NOT re-added. They are reported under unsubscribed and their opt-out stands; re-subscribing an opt-out is a deliberate act and this endpoint is the routine one.

New members start CONFIRMED on a SINGLE opt-in list and UNCONFIRMED on a DOUBLE one, decided by the list — there is no request field that overrides it. Note that on a DOUBLE list this endpoint sends no confirmation email, so those members stay unmailable until they confirm through one of your own flows.

Automations do not run on membership added this way: a "subscriber joins list" trigger will not fire for these people.

https://api.mailneo.co/api/v2
POST/lists/{listId}/subscriberslists:write

Example request

curl -X POST "https://api.mailneo.co/api/v2/lists/{listId}/subscribers" \
  -H "X-API-Key: $MAILNEO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "subscriber_ids": [
      "<string>"
    ]
  }'
Authorization
Required scope
lists:write. A key without it is refused before the endpoint runs.
Credential
X-API-Key: mk_live_...Authorization: Bearer mk_live_...
Idempotency
Idempotency-Key is optional.
Path parameters1
NameTypeRequiredNotes
listIdstringYesmax 64 chars
Query parameters1
NameTypeRequiredNotes
dry_runbooleanNoReport what this call WOULD do and commit nothing. Returns data: null and a meta.impact block. Requires the resource's :read scope in addition to the scope this endpoint declares. Must be exactly true or false.
Header parameters1
NameTypeRequiredNotes
Idempotency-KeystringNoA caller-chosen unique string, at most 255 characters. Retrying with the same key replays the first response instead of repeating the work; the replay carries Idempotent-Replayed: true. Reusing a key with a different body is a 409 idempotency_key_reused. Keys are remembered for 24 hours. · max 255 chars
Request body

Body

FieldTypeAlways presentNotes
subscriber_idsstring[]Yes
Response · 200

Success

dataMay be null.

FieldTypeAlways presentNotes
object"list_membership"Yes
list_idstringYes
addedstring[]Yes
already_memberstring[]Yes
unsubscribedstring[]Yes
not_foundstring[]Yes

meta

FieldTypeAlways presentNotes
request_idstringYes
dry_runbooleanYes
impactobjectNo
impact.object"impact"Yes
impact.would_createintegerYes
impact.would_updateintegerYes
impact.would_deleteintegerYes
impact.affected_countintegerYes
impact.affected_idsstring[]Yes
impact.quotaobject · nullableYes
impact.quota.kindstringYes
impact.quota.would_consumeintegerYes
impact.quota.limitinteger · nullableYes
impact.quota.currentintegerYes
Listswrite
removeListSubscriber

Unsubscribe a subscriber from a list

Removes one subscriber from this list's mailable membership by marking their subscription UNSUBSCRIBED.

The membership record is deliberately KEPT rather than deleted, which is why this answers with the membership rather than deleted: true. The record IS the opt-out: deleting it would leave nothing to say this person had ever left, and the next POST /lists/{listId}/subscribers would silently re-add them and start mailing them again. For the same reason this is not reversible through the API — re-adding them reports unsubscribed and changes nothing.

The subscriber itself is untouched and keeps every other list they are on. They still count towards this list's subscriber_count, which counts membership rather than mailability.

Calling this again is a no-op that succeeds. A subscriber who was never on the list is a 404.

https://api.mailneo.co/api/v2
DELETE/lists/{listId}/subscribers/{subscriberId}lists:write

Example request

curl -X DELETE "https://api.mailneo.co/api/v2/lists/{listId}/subscribers/{subscriberId}" \
  -H "X-API-Key: $MAILNEO_API_KEY"
Authorization
Required scope
lists:write. A key without it is refused before the endpoint runs.
Credential
X-API-Key: mk_live_...Authorization: Bearer mk_live_...
Path parameters2
NameTypeRequiredNotes
listIdstringYesmax 64 chars
subscriberIdstringYesmax 64 chars
Query parameters1
NameTypeRequiredNotes
dry_runbooleanNoReport what this call WOULD do and commit nothing. Returns data: null and a meta.impact block. Requires the resource's :read scope in addition to the scope this endpoint declares. Must be exactly true or false.
Response · 200

Success

dataMay be null.

FieldTypeAlways presentNotes
object"list_membership"Yes
list_idstringYes
subscriber_idstringYes
status"UNSUBSCRIBED"Yes

meta

FieldTypeAlways presentNotes
request_idstringYes
dry_runbooleanYes
impactobjectNo
impact.object"impact"Yes
impact.would_createintegerYes
impact.would_updateintegerYes
impact.would_deleteintegerYes
impact.affected_countintegerYes
impact.affected_idsstring[]Yes
impact.quotaobject · nullableYes
impact.quota.kindstringYes
impact.quota.would_consumeintegerYes
impact.quota.limitinteger · nullableYes
impact.quota.currentintegerYes

Next Steps