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 →
listListsList 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.
/listslists:readExample 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
| Name | Type | Required | Notes |
|---|---|---|---|
| limit | integer | No | default 25 · 1–100 |
| cursor | string | No | max 256 chars |
| order | "asc" | "desc" | No | default "desc" |
| updated_since | string · date-time | No | ISO 8601 |
| sort | "updated_at" | "created_at" | No | default "updated_at" |
| type | "PUBLIC" | "PRIVATE" | "TEMPORARY" | No | |
| optin | "SINGLE" | "DOUBLE" | No | |
| name | string | No | max 200 chars |
Response · 200
Success
data[]Each element of the array.
| Field | Type | Always present | Notes |
|---|---|---|---|
| object | "list" | Yes | |
| id | string | Yes | |
| name | string | Yes | |
| description | string | Yes | |
| type | string | Yes | |
| optin | string | Yes | |
| tags | string[] | Yes | |
| created_at | string | Yes | |
| updated_at | string | Yes |
meta
| Field | Type | Always present | Notes |
|---|---|---|---|
| request_id | string | Yes | |
| has_more | boolean | Yes | |
| next_cursor | string · nullable | Yes | |
| synced_through | string | No |
createListCreate 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.
/listslists:writeExample 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-Keyis optional.
Query parameters1
| Name | Type | Required | Notes |
|---|---|---|---|
| dry_run | boolean | No | Report 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
| Name | Type | Required | Notes |
|---|---|---|---|
| Idempotency-Key | string | No | A 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
| Field | Type | Always present | Notes |
|---|---|---|---|
| name | string | Yes | max 200 chars |
| description | string | No | max 500 chars |
| type | "PUBLIC" | "PRIVATE" | "TEMPORARY" | No | |
| optin | "SINGLE" | "DOUBLE" | No | |
| tags | string[] | No |
Response · 200
Success
data
| Field | Type | Always present | Notes |
|---|---|---|---|
| data | object | object · nullable | Yes |
meta
| Field | Type | Always present | Notes |
|---|---|---|---|
| request_id | string | Yes | |
| dry_run | boolean | Yes | |
| impact | object | No | |
| impact.object | "impact" | Yes | |
| impact.would_create | integer | Yes | |
| impact.would_update | integer | Yes | |
| impact.would_delete | integer | Yes | |
| impact.affected_count | integer | Yes | |
| impact.affected_ids | string[] | Yes | |
| impact.quota | object · nullable | Yes | |
| impact.quota.kind | string | Yes | |
| impact.quota.would_consume | integer | Yes | |
| impact.quota.limit | integer · nullable | Yes | |
| impact.quota.current | integer | Yes |
Response · 201
Created
data
| Field | Type | Always present | Notes |
|---|---|---|---|
| data | object | object · nullable | Yes |
meta
| Field | Type | Always present | Notes |
|---|---|---|---|
| request_id | string | Yes | |
| dry_run | boolean | Yes | |
| impact | object | No | |
| impact.object | "impact" | Yes | |
| impact.would_create | integer | Yes | |
| impact.would_update | integer | Yes | |
| impact.would_delete | integer | Yes | |
| impact.affected_count | integer | Yes | |
| impact.affected_ids | string[] | Yes | |
| impact.quota | object · nullable | Yes | |
| impact.quota.kind | string | Yes | |
| impact.quota.would_consume | integer | Yes | |
| impact.quota.limit | integer · nullable | Yes | |
| impact.quota.current | integer | Yes |
getListRetrieve a list
Includes subscriber_count: every subscriber who is a member of this list, whatever their status and whatever their per-list subscription state.
/lists/{listId}lists:readExample 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
| Name | Type | Required | Notes |
|---|---|---|---|
| listId | string | Yes | max 64 chars |
Response · 200
Success
data
| Field | Type | Always present | Notes |
|---|---|---|---|
| object | "list" | Yes | |
| id | string | Yes | |
| name | string | Yes | |
| description | string | Yes | |
| type | string | Yes | |
| optin | string | Yes | |
| tags | string[] | Yes | |
| created_at | string | Yes | |
| updated_at | string | Yes | |
| subscriber_count | integer | Yes |
meta
| Field | Type | Always present | Notes |
|---|---|---|---|
| request_id | string | Yes |
updateListUpdate 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.
/lists/{listId}lists:writeExample 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-Keyis optional.
Path parameters1
| Name | Type | Required | Notes |
|---|---|---|---|
| listId | string | Yes | max 64 chars |
Query parameters1
| Name | Type | Required | Notes |
|---|---|---|---|
| dry_run | boolean | No | Report 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
| Name | Type | Required | Notes |
|---|---|---|---|
| Idempotency-Key | string | No | A 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
| Field | Type | Always present | Notes |
|---|---|---|---|
| name | string | No | max 200 chars |
| description | string | No | max 500 chars |
| type | "PUBLIC" | "PRIVATE" | "TEMPORARY" | No | |
| optin | "SINGLE" | "DOUBLE" | No | |
| tags | string[] | No |
Response · 200
Success
data
| Field | Type | Always present | Notes |
|---|---|---|---|
| data | object | object · nullable | Yes |
meta
| Field | Type | Always present | Notes |
|---|---|---|---|
| request_id | string | Yes | |
| dry_run | boolean | Yes | |
| impact | object | No | |
| impact.object | "impact" | Yes | |
| impact.would_create | integer | Yes | |
| impact.would_update | integer | Yes | |
| impact.would_delete | integer | Yes | |
| impact.affected_count | integer | Yes | |
| impact.affected_ids | string[] | Yes | |
| impact.quota | object · nullable | Yes | |
| impact.quota.kind | string | Yes | |
| impact.quota.would_consume | integer | Yes | |
| impact.quota.limit | integer · nullable | Yes | |
| impact.quota.current | integer | Yes |
deleteListDelete 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.
/lists/{listId}lists:writeExample 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
| Name | Type | Required | Notes |
|---|---|---|---|
| listId | string | Yes | max 64 chars |
Query parameters1
| Name | Type | Required | Notes |
|---|---|---|---|
| dry_run | boolean | No | Report 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.
| Field | Type | Always present | Notes |
|---|---|---|---|
| object | "list" | Yes | |
| id | string | Yes | |
| deleted | true | Yes |
meta
| Field | Type | Always present | Notes |
|---|---|---|---|
| request_id | string | Yes | |
| dry_run | boolean | Yes | |
| impact | object | No | |
| impact.object | "impact" | Yes | |
| impact.would_create | integer | Yes | |
| impact.would_update | integer | Yes | |
| impact.would_delete | integer | Yes | |
| impact.affected_count | integer | Yes | |
| impact.affected_ids | string[] | Yes | |
| impact.quota | object · nullable | Yes | |
| impact.quota.kind | string | Yes | |
| impact.quota.would_consume | integer | Yes | |
| impact.quota.limit | integer · nullable | Yes | |
| impact.quota.current | integer | Yes |
listListSubscribersList 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.
/lists/{listId}/subscriberssubscribers:readExample 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
| Name | Type | Required | Notes |
|---|---|---|---|
| listId | string | Yes | max 64 chars |
Query parameters9
| Name | Type | Required | Notes |
|---|---|---|---|
| limit | integer | No | default 25 · 1–100 |
| cursor | string | No | max 256 chars |
| order | "asc" | "desc" | No | default "desc" |
| updated_since | string · date-time | No | ISO 8601 |
| sort | "updated_at" | "created_at" | No | default "updated_at" |
| status | "ENABLED" | "DISABLED" | "BLOCKLISTED" | No | |
| string | No | max 320 chars | |
| source | string | No | max 200 chars |
| tags | string | No | max 1000 chars |
Response · 200
Success
data[]Each element of the array.
| Field | Type | Always present | Notes |
|---|---|---|---|
| object | "subscriber" | Yes | |
| id | string | Yes | |
| string | Yes | ||
| name | string | Yes | |
| first_name | string · nullable | Yes | |
| last_name | string · nullable | Yes | |
| phone | string · nullable | Yes | |
| company | string · nullable | Yes | |
| status | string | Yes | |
| tags | string[] | Yes | |
| source | string · nullable | Yes | |
| custom_fields | object | Yes | open keys |
| last_contacted_at | string · nullable | Yes | |
| created_at | string | Yes | |
| updated_at | string | Yes |
meta
| Field | Type | Always present | Notes |
|---|---|---|---|
| request_id | string | Yes | |
| has_more | boolean | Yes | |
| next_cursor | string · nullable | Yes | |
| synced_through | string | No |
addListSubscribersAdd 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.
/lists/{listId}/subscriberslists:writeExample 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-Keyis optional.
Path parameters1
| Name | Type | Required | Notes |
|---|---|---|---|
| listId | string | Yes | max 64 chars |
Query parameters1
| Name | Type | Required | Notes |
|---|---|---|---|
| dry_run | boolean | No | Report 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
| Name | Type | Required | Notes |
|---|---|---|---|
| Idempotency-Key | string | No | A 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
| Field | Type | Always present | Notes |
|---|---|---|---|
| subscriber_ids | string[] | Yes |
Response · 200
Success
dataMay be null.
| Field | Type | Always present | Notes |
|---|---|---|---|
| object | "list_membership" | Yes | |
| list_id | string | Yes | |
| added | string[] | Yes | |
| already_member | string[] | Yes | |
| unsubscribed | string[] | Yes | |
| not_found | string[] | Yes |
meta
| Field | Type | Always present | Notes |
|---|---|---|---|
| request_id | string | Yes | |
| dry_run | boolean | Yes | |
| impact | object | No | |
| impact.object | "impact" | Yes | |
| impact.would_create | integer | Yes | |
| impact.would_update | integer | Yes | |
| impact.would_delete | integer | Yes | |
| impact.affected_count | integer | Yes | |
| impact.affected_ids | string[] | Yes | |
| impact.quota | object · nullable | Yes | |
| impact.quota.kind | string | Yes | |
| impact.quota.would_consume | integer | Yes | |
| impact.quota.limit | integer · nullable | Yes | |
| impact.quota.current | integer | Yes |
removeListSubscriberUnsubscribe 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.
/lists/{listId}/subscribers/{subscriberId}lists:writeExample 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
| Name | Type | Required | Notes |
|---|---|---|---|
| listId | string | Yes | max 64 chars |
| subscriberId | string | Yes | max 64 chars |
Query parameters1
| Name | Type | Required | Notes |
|---|---|---|---|
| dry_run | boolean | No | Report 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.
| Field | Type | Always present | Notes |
|---|---|---|---|
| object | "list_membership" | Yes | |
| list_id | string | Yes | |
| subscriber_id | string | Yes | |
| status | "UNSUBSCRIBED" | Yes |
meta
| Field | Type | Always present | Notes |
|---|---|---|---|
| request_id | string | Yes | |
| dry_run | boolean | Yes | |
| impact | object | No | |
| impact.object | "impact" | Yes | |
| impact.would_create | integer | Yes | |
| impact.would_update | integer | Yes | |
| impact.would_delete | integer | Yes | |
| impact.affected_count | integer | Yes | |
| impact.affected_ids | string[] | Yes | |
| impact.quota | object · nullable | Yes | |
| impact.quota.kind | string | Yes | |
| impact.quota.would_consume | integer | Yes | |
| impact.quota.limit | integer · nullable | Yes | |
| impact.quota.current | integer | Yes |