Campaign Monitor lists and preferences
Discover configured lists, read member state and request selected provider list changes. Public list discovery and member preference operations have different access contexts.
Operation choices
| Task | Operation | Access |
|---|---|---|
| Discover shown lists | Discover shown lists | Guest/resource |
| Read member list state | Read member list state | Active member/resource |
| Subscribe selected lists | Subscribe selected lists | Active member/resource |
| Unsubscribe selected lists | Unsubscribe selected lists | Active member/resource |
Example clients and synthetic values follow response conventions.
Member and provider context
Use an existing active member and valid resource with custom token and resource headers. Shared guards reject inactive users and locked/suspended resource relationships. The resource must already have Campaign Monitor OAuth configuration. These member operations use API identity resolution, including configured Firebase where applicable; see credential transport.
Public list discovery has a separate guest/resource context. For member reads and changes, use the target-selection rule below. The next section explains result filters and cache effects. Each operation describes its provider writes and errors.
Selecting another member
The following role check applies to user_id in member reads and mutations. It differs from Mailchimp’s global admin/root-only check.
| Caller context | Can nonempty user_id select another existing user? |
|---|---|
| Root caller | Yes. |
| Nonroot caller with a relationship to the selected resource | Yes only when that relationship’s role is readonly, manager, support, admin, partner or root. An ordinary relationship blocks global-role fallback. |
| Nonroot caller without a relationship to the selected resource | Yes only when the caller’s global role is readonly, manager, support, admin, partner or root. |
| Other caller | No; user_id is ignored and the current member is used. |
Omitting user_id selects the current member even for an eligible caller. The selected target must exist, but this lookup does not require the target to belong to the selected resource. The caller still needs the operation’s active-member/resource context.
Member result selection and cache effects
Ordinary/current-member output includes rows whose show_list is strictly true. With an eligible caller’s nonempty user_id, output also includes rows with show_list, require_list or allowed_list truthy. That output filter is separate from mutation eligibility. Reads force fresh provider queries, bypassing the email-key cache read but writing that cache for 600 seconds. The state reread itself does not change provider membership; its cache write is a local side effect. Mutation calls perform the separate writes described in their own sections.
Discover shown lists
GET /api/v1/integrations/campaignmonitor/get-lists
Read shown list records stored for the selected Wallkit resource. This is a local configuration read, not a provider list refresh or a member-state check.
Before you call
Guest access is permitted, with a valid resource header and existing Campaign Monitor OAuth configuration. No member token is required. This public operation does not apply the member API’s namespace-specific Firebase user-selection branch, even though both use the same base controller. See credential transport.
Request
No request body or action-specific query parameters.
| Name | Location | Type / requirement | Meaning |
|---|---|---|---|
| resource | header | resource public key; required | Selects stored configured list records. |
Result
HTTP 200 with items array, sorted by list_name. Rows whose show_list is false after Boolean conversion are omitted; no pagination or live provider call.
| Field | Type / presence | Meaning |
|---|---|---|
| items[].id | stored Wallkit integer / null | Local resource-list row ID; do not send it as provider list_id. |
| items[].list_id | stored provider identifier string / null | Provider list key used in subscribe/unsubscribe. |
| items[].list_name, items[].client_name | stored string / null | List and provider-client display names. |
| items[].confirmed_opt | stored flag / null; no explicit cast | Configured opt-in setting, not member confirmation. |
| items[].unsubscribe_settings | stored flag / null; no explicit cast | True stored for AllClientLists setting; not an executed unsubscription. |
Example: Discover a newsletter list
curl "${WALLKIT_API_BASE}/api/v1/integrations/campaignmonitor/get-lists" \
-H "resource: ${RESOURCE_KEY}"
const response = await fetch(`${process.env.WALLKIT_API_BASE}/api/v1/integrations/campaignmonitor/get-lists`, {
method: "GET", headers: { "resource": process.env.RESOURCE_KEY }
});
console.log(response.status, await response.json());
import os, json
from urllib.request import Request, urlopen
from urllib.error import HTTPError
request = Request(os.environ["WALLKIT_API_BASE"] + "/api/v1/integrations/campaignmonitor/get-lists",
headers={"resource": os.environ["RESOURCE_KEY"]}, method="GET")
try:
with urlopen(request) as response:
print(response.status, json.load(response))
except HTTPError as error:
print(error.code, json.load(error))
Synthetic HTTP 200 excerpt:
{
"items": [
{
"id": 1001,
"list_id": "list-example",
"list_name": "Technology",
"client_name": "Example publication",
"confirmed_opt": true,
"unsubscribe_settings": false
}
]
}
Keep local ID 1001 separate from provider list-example. The result does not say this member is subscribed.
Consequential alternate
Synthetic HTTP 200 excerpt:
{
"items": []
}
No shown stored rows are available. Other require_list/allowed_list rows may still be eligible for member mutations; this public list is not the full write allowlist.
Recovery
| HTTP status | Code / shape | Cause | Next action |
|---|---|---|---|
| 500 | integration_error / Campaignmonitor integration not configurated | Resource lacks OAuth configuration. | Ask the integration owner to check the existing integration. |
| 404 | resource_not_exists / Incorrect resource key | Resource resolution failed. | Check the resource public key. |
| 200 | items:[] | No stored rows pass show_list. | Handle no displayed choices; ask the owner about intended shown configuration. |
| Shared failure | No dedicated action error shape | Local record lookup throws. | Have the owner inspect the resource configuration. |
Next task
With existing member context, use read member state to see provider flags for shown lists. Supply provider list_id, not local id, when constructing a mutation.
Read member list state
GET /api/v1/integrations/campaignmonitor/interests
Read current provider state for the selected member across visible configured lists. The GET performs synchronous provider reads and writes a local cache; it is separate from public stored-list discovery.
Before you call
Use the member/provider context and target-selection role check; omission selects the current member.
Request
No request body.
| Name | Location | Type / requirement | Meaning |
|---|---|---|---|
| resource | header | resource public key; required | Selects the existing integration. |
| token | header | member session token; required | Selects caller identity for member operations. |
| user_id | query | optional integer-filtered Wallkit ID | Honored only when the caller passes the target-selection role check; otherwise ignored. |
Result
HTTP 200 with lists, an array that can be empty. This combines stored list configuration with synchronous provider state, not raw subscriber data.
| Field | Type / presence | Meaning |
|---|---|---|
| lists[].list_id | stored provider identifier string / null | Provider list key; use this in mutation lists. |
| lists[].list_name | stored string / null | Configured display name. |
| lists[].confirmed_opt | stored flag / null; no explicit cast | Stored confirmed-opt-in setting, not evidence of this member’s consent/confirmation. |
| lists[].unsubscribe_settings | stored flag / null; no explicit cast | True is stored for provider AllClientLists setting; not proof that this request changed other lists. |
| lists[].subscribed | Boolean | True only when returned state is Active; false for other or blank states. |
| lists[].state | provider string / null; blank possible | Returned provider State; no complete enum guarantee. Blank can follow a caught provider client error, not just absence. |
| lists[].updating | Boolean false | Fixed value in this projection; not a queue/progress tracker. |
See member result selection and cache effects for the returned-list filter and state reread.
Example: Read the current member’s active list
curl "${WALLKIT_API_BASE}/api/v1/integrations/campaignmonitor/interests" \
-H "resource: ${RESOURCE_KEY}" \
-H "token: ${USER_TOKEN}"
const response = await fetch(`${process.env.WALLKIT_API_BASE}/api/v1/integrations/campaignmonitor/interests`, {
method: "GET", headers: { "resource": process.env.RESOURCE_KEY, "token": process.env.USER_TOKEN }
});
console.log(response.status, await response.json());
import os, json
from urllib.request import Request, urlopen
from urllib.error import HTTPError
request = Request(os.environ["WALLKIT_API_BASE"] + "/api/v1/integrations/campaignmonitor/interests",
headers={"resource": os.environ["RESOURCE_KEY"], "token": os.environ["USER_TOKEN"]}, method="GET")
try:
with urlopen(request) as response:
print(response.status, json.load(response))
except HTTPError as error:
print(error.code, json.load(error))
Synthetic HTTP 200 excerpt:
{
"lists": [
{
"list_id": "list-example",
"list_name": "Technology",
"confirmed_opt": true,
"unsubscribe_settings": false,
"subscribed": true,
"state": "Active",
"updating": false
}
]
}
Active maps to subscribed:true. Fixed updating:false does not mean no other work exists.
Consequential alternate
Synthetic HTTP 200 excerpt with blank provider state:
{
"lists": [
{
"list_id": "list-example",
"subscribed": false,
"state": "",
"updating": false
}
]
}
A provider ClientException can become this blank state. Keep unknown/failed lookup distinct from a confirmed unsubscription.
Recovery
| HTTP status | Code / shape | Cause | Next action |
|---|---|---|---|
| 500 | integration_error / Campaignmonitor integration not configurated | Resource lacks OAuth configuration. | Ask the integration owner to check the selected resource’s existing integration. |
| 404 | user_not_found / User not found | Eligible caller’s selected user ID did not resolve. | Check the intended existing Wallkit user ID. |
| Shared failure | No stable action-specific provider error shape | Forced reread/adapter construction throws outside a handler catch. | Inspect the reported provider condition; account for earlier writes on mutation calls. |
| 200 | lists:[] / blank state | No selected visible rows, or a caught provider client error. | Handle empty/unknown state and ask the owner to inspect unexpected provider results. |
Next task
Use subscribe or unsubscribe only when a selected provider change is intended. Treat blank state as unknown before choosing.
Subscribe selected lists
POST /api/v1/integrations/campaignmonitor/subscribe
Request provider subscribe changes for selected lists. IDs outside the resource’s show_list OR require_list OR allowed_list allowlist are silently skipped, and omitted lists are untouched. This allowlist is broader than public shown-list discovery.
Before you call
Use the member/provider context and target-selection role check; omission selects the current member.
For each eligible list, build member name/email and configured custom fields, then read provider state and add or update the subscriber. Existing state uses an update path. Missing mapped values become empty strings; mapped values over 250 characters fail that list. The adapter sends Resubscribe:true, RestartSubscriptionBasedAutoresponders:true and ConsentToTrack:Yes. These are adapter defaults, not caller-supplied consent evidence. This body does not supply arbitrary merge fields.
Request
Send a JSON object.
| Name | Location | Type / requirement | Meaning |
|---|---|---|---|
| resource | header | resource public key; required | Selects the existing integration. |
| token | header | member session token; required | Selects caller identity for member operations. |
| lists | JSON | nonempty array of provider list IDs; required | IDs from existing configured lists, not local row IDs. No deduplication; duplicate IDs can repeat calls. |
| user_id | JSON | optional integer-filtered Wallkit ID | Selects another user only when the caller passes the target-selection role check; default current member. |
Email/name come from the selected member; direct email, custom-field or consent overrides are not parsed from this body.
Result
HTTP 200 with lists, an array that can be empty. This combines stored list configuration with synchronous provider state, not raw subscriber data.
| Field | Type / presence | Meaning |
|---|---|---|
| lists[].list_id | stored provider identifier string / null | Provider list key; use this in mutation lists. |
| lists[].list_name | stored string / null | Configured display name. |
| lists[].confirmed_opt | stored flag / null; no explicit cast | Stored confirmed-opt-in setting, not evidence of this member’s consent/confirmation. |
| lists[].unsubscribe_settings | stored flag / null; no explicit cast | True is stored for provider AllClientLists setting; not proof that this request changed other lists. |
| lists[].subscribed | Boolean | True only when returned state is Active; false for other or blank states. |
| lists[].state | provider string / null; blank possible | Returned provider State; no complete enum guarantee. Blank can follow a caught provider client error, not just absence. |
| lists[].updating | Boolean false | Fixed value in this projection; not a queue/progress tracker. |
See member result selection and cache effects for the returned-list filter and state reread.
Mutations run sequentially and then force a state reread. A caught per-list exception adds error/error_description/req_guid while final status stays HTTP 200 and other lists can continue. A later reread failure can happen after earlier changes. No rollback, deduplication, provider delivery, consent or membership guarantee. A provider ClientException in the add path can return false without an outward error; rely on returned state with its blank-state limitation.
| Extra field | Type / presence | Meaning |
|---|---|---|
| error | string; conditional | error_synchronization after caught per-list failure. |
| error_description | string; conditional | CM subscribe error. |
| req_guid | string; with error or shared debug data | Correlation value, not a provider transaction ID. |
Example: subscribe selected lists for the current member
curl -X POST "${WALLKIT_API_BASE}/api/v1/integrations/campaignmonitor/subscribe" \
-H "resource: ${RESOURCE_KEY}" \
-H "token: ${USER_TOKEN}" \
-H "Content-Type: application/json" \
--data-raw '{"lists": ["list-example"]}'
const response = await fetch(`${process.env.WALLKIT_API_BASE}/api/v1/integrations/campaignmonitor/subscribe`, {
method: "POST", headers: { "resource": process.env.RESOURCE_KEY, "token": process.env.USER_TOKEN, "Content-Type": "application/json" },
body: JSON.stringify({"lists": ["list-example"]})
});
console.log(response.status, await response.json());
import os, json
from urllib.request import Request, urlopen
from urllib.error import HTTPError
body = {'lists': ['list-example']}
request = Request(os.environ["WALLKIT_API_BASE"] + "/api/v1/integrations/campaignmonitor/subscribe",
data=json.dumps(body).encode("utf-8"), headers={"resource": os.environ["RESOURCE_KEY"], "token": os.environ["USER_TOKEN"], "Content-Type": "application/json"}, method="POST")
try:
with urlopen(request) as response:
print(response.status, json.load(response))
except HTTPError as error:
print(error.code, json.load(error))
Synthetic HTTP 200 excerpt:
{
"lists": [
{
"list_id": "list-example",
"subscribed": true,
"state": "Active",
"updating": false
}
]
}
This excerpt omits stored display/settings fields. Read the provider state rather than interpreting updating:false as completion.
Consequential alternate
Synthetic HTTP 200 excerpt after a caught list failure:
{
"error": "error_synchronization",
"error_description": "CM subscribe error",
"req_guid": "example-request",
"lists": [
{
"list_id": "list-example",
"subscribed": false,
"state": "",
"updating": false
}
]
}
HTTP 200 can include a failure. Other lists may already have changed; inspect each returned state and ask the owner about the failed list before repeating. Blank state is not proof of unsubscription.
Recovery
| HTTP status | Code / shape | Cause | Next action |
|---|---|---|---|
| 500 | integration_error / Campaignmonitor integration not configurated | Resource lacks OAuth configuration. | Ask the integration owner to check the selected resource’s existing integration. |
| 404 | user_not_found / User not found | Eligible caller’s selected user ID did not resolve. | Check the intended existing Wallkit user ID. |
| Shared failure | No stable action-specific provider error shape | Forced reread/adapter construction throws outside a handler catch. | Inspect the reported provider condition; account for earlier writes on mutation calls. |
| 406 | incorrect_data / Body must be json format | No truthy parsed JSON body. | Send a JSON object with Content-Type: application/json. |
| 406 | incorrect_data / Subscribe lists is empty | lists absent/empty, including for unsubscribe. | Supply the intended provider list IDs. |
| 200 | error_synchronization / CM subscribe error | Per-list operation threw. | Handle partial state; inspect the failed list before another write. |
| 200 | No change / blank state | IDs skipped or provider result unconfirmed. | Check eligibility and provider state; do not equate HTTP success with mutation success. |
Next task
Inspect each returned state and any error before displaying a successful preference change. A later member state read can refresh the display; it does not repair a failed write.
Unsubscribe selected lists
POST /api/v1/integrations/campaignmonitor/unsubscribe
Request provider unsubscribe changes for selected lists. IDs outside the resource’s show_list OR require_list OR allowed_list allowlist are silently skipped, and omitted lists are untouched. This allowlist is broader than public shown-list discovery.
Before you call
Use the member/provider context and target-selection role check; omission selects the current member.
For each eligible list, read provider state first. Missing/blank, Unsubscribed, Unconfirmed and Bounced states skip the unsubscribe POST. Other states trigger a provider unsubscribe call. No deletion of the subscriber record or all-list atomicity is promised.
Request
Send a JSON object.
| Name | Location | Type / requirement | Meaning |
|---|---|---|---|
| resource | header | resource public key; required | Selects the existing integration. |
| token | header | member session token; required | Selects caller identity for member operations. |
| lists | JSON | nonempty array of provider list IDs; required | IDs from existing configured lists, not local row IDs. No deduplication; duplicate IDs can repeat calls. |
| user_id | JSON | optional integer-filtered Wallkit ID | Selects another user only when the caller passes the target-selection role check; default current member. |
Email/name come from the selected member; direct email, custom-field or consent overrides are not parsed from this body.
Result
HTTP 200 with lists, an array that can be empty. This combines stored list configuration with synchronous provider state, not raw subscriber data.
| Field | Type / presence | Meaning |
|---|---|---|
| lists[].list_id | stored provider identifier string / null | Provider list key; use this in mutation lists. |
| lists[].list_name | stored string / null | Configured display name. |
| lists[].confirmed_opt | stored flag / null; no explicit cast | Stored confirmed-opt-in setting, not evidence of this member’s consent/confirmation. |
| lists[].unsubscribe_settings | stored flag / null; no explicit cast | True is stored for provider AllClientLists setting; not proof that this request changed other lists. |
| lists[].subscribed | Boolean | True only when returned state is Active; false for other or blank states. |
| lists[].state | provider string / null; blank possible | Returned provider State; no complete enum guarantee. Blank can follow a caught provider client error, not just absence. |
| lists[].updating | Boolean false | Fixed value in this projection; not a queue/progress tracker. |
See member result selection and cache effects for the returned-list filter and state reread.
Mutations run sequentially and then force a state reread. A caught per-list exception adds error/error_description/req_guid while final status stays HTTP 200 and other lists can continue. A later reread failure can happen after earlier changes. No rollback, deduplication, provider delivery, consent or membership guarantee. The response does not return each skipped-list reason; subscribed:false alone cannot distinguish all outcomes.
| Extra field | Type / presence | Meaning |
|---|---|---|
| error | string; conditional | error_synchronization after caught per-list failure. |
| error_description | string; conditional | CM unsubscribe error. |
| req_guid | string; with error or shared debug data | Correlation value, not a provider transaction ID. |
Example: unsubscribe selected lists for the current member
curl -X POST "${WALLKIT_API_BASE}/api/v1/integrations/campaignmonitor/unsubscribe" \
-H "resource: ${RESOURCE_KEY}" \
-H "token: ${USER_TOKEN}" \
-H "Content-Type: application/json" \
--data-raw '{"lists": ["list-example"]}'
const response = await fetch(`${process.env.WALLKIT_API_BASE}/api/v1/integrations/campaignmonitor/unsubscribe`, {
method: "POST", headers: { "resource": process.env.RESOURCE_KEY, "token": process.env.USER_TOKEN, "Content-Type": "application/json" },
body: JSON.stringify({"lists": ["list-example"]})
});
console.log(response.status, await response.json());
import os, json
from urllib.request import Request, urlopen
from urllib.error import HTTPError
body = {'lists': ['list-example']}
request = Request(os.environ["WALLKIT_API_BASE"] + "/api/v1/integrations/campaignmonitor/unsubscribe",
data=json.dumps(body).encode("utf-8"), headers={"resource": os.environ["RESOURCE_KEY"], "token": os.environ["USER_TOKEN"], "Content-Type": "application/json"}, method="POST")
try:
with urlopen(request) as response:
print(response.status, json.load(response))
except HTTPError as error:
print(error.code, json.load(error))
Synthetic HTTP 200 excerpt:
{
"lists": [
{
"list_id": "list-example",
"subscribed": false,
"state": "Unsubscribed",
"updating": false
}
]
}
This excerpt omits stored display/settings fields. Read the provider state rather than interpreting updating:false as completion.
Consequential alternate
Synthetic HTTP 200 excerpt after a caught list failure:
{
"error": "error_synchronization",
"error_description": "CM unsubscribe error",
"req_guid": "example-request",
"lists": [
{
"list_id": "list-example",
"subscribed": false,
"state": "",
"updating": false
}
]
}
HTTP 200 can include a failure. Other lists may already have changed; inspect each returned state and ask the owner about the failed list before repeating. Blank state is not proof of unsubscription.
Recovery
| HTTP status | Code / shape | Cause | Next action |
|---|---|---|---|
| 500 | integration_error / Campaignmonitor integration not configurated | Resource lacks OAuth configuration. | Ask the integration owner to check the selected resource’s existing integration. |
| 404 | user_not_found / User not found | Eligible caller’s selected user ID did not resolve. | Check the intended existing Wallkit user ID. |
| Shared failure | No stable action-specific provider error shape | Forced reread/adapter construction throws outside a handler catch. | Inspect the reported provider condition; account for earlier writes on mutation calls. |
| 406 | incorrect_data / Body must be json format | No truthy parsed JSON body. | Send a JSON object with Content-Type: application/json. |
| 406 | incorrect_data / Subscribe lists is empty | lists absent/empty, including for unsubscribe. | Supply the intended provider list IDs. |
| 200 | error_synchronization / CM unsubscribe error | Per-list operation threw. | Handle partial state; inspect the failed list before another write. |
| 200 | No change / blank state | IDs skipped or provider result unconfirmed. | Check eligibility and provider state; do not equate HTTP success with mutation success. |
Next task
Inspect each returned state and any error before confirming unsubscription. For Wallkit membership tasks use subscriptions; provider list unsubscription does not cancel that membership.
Follow the task guide
Read the 3rd party integration flows for request order, context choices and response decisions.