Prenumerationslistor
Subscription lists let people sign up for your newsletters and sendouts. Lists are created and managed in Memlist, and there are two ways to put a signup on your own website:
- Link to the hosted form - every list has a ready-made public page. No code required.
- Build your own form - a public JSON API lets you implement the signup and unsubscribe flow entirely in your own design.
There is also an authenticated admin API for creating and managing lists and subscribers, and for syncing subscribers from another system, such as a web shop or a CRM.
Option 1 - Link to the hosted form
Every subscription list has a public form hosted by your Memlist instance:
https://<instance>.memlist.se/subscribe/<list_uuid>
The page renders the list's name, description, image, fields, optional questions and consent checkbox, and handles the subscription for you. Link to it or embed it in an iframe.
You find the list's uuid in Memlist where the list is managed, or via the admin API
(GET /subscription_list/{id}).
Option 2 - Build your own form
The public endpoints require no authentication. Base URL:
https://<instance>.memlist.se/api/v3
GET /subscription_list/public/{uuid}
Fetch the list's public info - use it to render your form.
curl -s "https://<instance>.memlist.se/api/v3/subscription_list/public/<list_uuid>"
Response - 200 OK:
{
"name": "Nyhetsbrev",
"description": "Vårt månatliga nyhetsbrev.",
"fields": ["firstname", "lastname"],
"uuid": "9c1b2f4e-77aa-4f0c-9a41-2f8d0c3e5b21",
"image_url": null,
"show_image": false,
"design": null,
"term": { "id": 12, "name": "Samtycke", "html": "<p>...</p>" },
"questions": [
{
"prop_id": 44,
"name": "Intresserad av evenemang",
"descr": "Få inbjudningar till våra evenemang",
"group_name": "Intressen",
"group_descr": null
}
]
}
fields- which optional fields the form should show (firstname,lastname,phone).emailis always required.term- an optional consent text configured on the list, ornull. If present, show it with a checkbox and sendterms_accepted: truewhen the visitor accepts.questions- optional checkboxes configured on the list. Send the ids the visitor ticked asanswered_prop_ids.
Returns 404 if the list does not exist or is inactive.
POST /subscription_list/public/{uuid}
Subscribe a visitor to the list.
curl -s -X POST "https://<instance>.memlist.se/api/v3/subscription_list/public/<list_uuid>" \
-H 'Content-Type: application/json' \
-d '{
"email": "anna@example.com",
"firstname": "Anna",
"lastname": "Andersson",
"answered_prop_ids": [44],
"terms_accepted": true
}'
| Field | Type | Required | Description |
|---|---|---|---|
email | string | Yes | Max 254 characters |
firstname | string | No | Max 128 characters |
lastname | string | No | Max 128 characters |
phone | string | No | Max 64 characters |
answered_prop_ids | number[] | No | Ids of ticked questions from the list's questions |
terms_accepted | boolean | No | Send true when the visitor accepted the list's term |
Response - 201 Created:
{ "member_id": 12345, "subscription": { "...": "..." } }
What happens on the Memlist side:
- If a member with that email already exists in the list's organization, they are subscribed (re-subscribing is always allowed). Otherwise a new member is created.
- Ticked questions are stored as attributes on the member - only ids that actually belong to the list's configured question set are accepted.
- If the list has a consent text and
terms_accepted: truewas sent, the acceptance is recorded on the member under Samtycken.
Errors:
| Status | Body | When |
|---|---|---|
400 | validation payload | Missing or invalid email |
404 | { "reason": "list not found" } | Unknown or inactive list |
429 | { "reason": "subscription temporarily unavailable" } | The list's subscriber cap was reached |
GET /subscription_list/public/company/{company_id}
Lists an organization's active subscription lists - useful when you want to offer several opt-ins at once.
Response - 200 OK:
[
{ "id": 3, "name": "Nyhetsbrev", "description": "Vårt månatliga nyhetsbrev." },
{ "id": 7, "name": "Evenemang", "description": null }
]
Note that this endpoint intentionally returns no uuid - take the uuids for your
signup forms from Memlist or the admin API.
Unsubscribe
Sendouts from Memlist already include working unsubscribe links, so if you only use the API to collect subscribers you do not need to implement anything here.
For a custom flow there are two public endpoints:
GET /subscription_list/unsubscribe/{list_uuid}/{member_uuid}- renders a hosted confirmation page. The GET itself never changes anything (so email security scanners cannot unsubscribe people by prefetching the link); the state change happens when the visitor clicks the confirm button.POST /subscription_list/unsubscribe/{list_uuid}/{member_uuid}- unsubscribes the member from that list. The response is always a generic{ "ok": true }, regardless of whether the member or list existed, so the endpoint cannot be used to probe who is subscribed.
There is also a global opt-out variant (POST /subscription_list/unsubscribe/all/{member_uuid})
that removes the member from all future sendouts. It requires a signed token
that only Memlist's own sendout links carry - a custom implementation should use the
per-list variant above.
Rate limits
| Endpoints | Limit |
|---|---|
Public GET endpoints | 120 requests per minute per IP |
Public POST endpoints | 20 requests per minute per IP |
Admin API
Managing lists requires an access token - see Authentication - and an access group that includes the subscription list module.
| Method | Path | Purpose |
|---|---|---|
GET | /subscription_list?company_id={company_id} | List an organization's subscription lists |
GET | /subscription_list/{id} | Get one list, including its uuid |
POST | /subscription_list | Create a list - name and company_id required; optional description, fields |
PUT | /subscription_list/{id} | Update name, description, fields, is_active and the list's image, design, consent term and questions |
DELETE | /subscription_list/{id} | Delete a list |
GET | /subscription_list/{id}/subscribers | List the list's subscribers |
GET | /subscription_list/member/{member_id} | The member's available lists with an is_subscribed flag |
POST | /subscription_list/{id}/subscribe/{member_id} | Subscribe a member |
POST | /subscription_list/{id}/unsubscribe/{member_id} | Unsubscribe a member |
POST | /subscription_list/subscribers | Put many email addresses on one or more lists, see below |
POST /subscription_list/subscribers
Puts email addresses, with optional contact details, on one or more lists in a single call. Use it to sync sign-ups from another system, such as a web shop, a CRM or a form. Call it from your backend, never from a browser - it needs an access token, see Authentication.
The API user needs access to the company that owns each list. No other permission is needed.
Take the list uuids from GET /subscription_list?company_id={company_id}, or from the
last part of the list's hosted form address, /subscribe/<list_uuid>.
curl -s -X POST "https://<instance>.memlist.se/api/v3/subscription_list/subscribers" \
-H "Authorization: Bearer <access_token>" \
-H 'Content-Type: application/json' \
-d '{
"uuids": ["9c1b2f4e-77aa-4f0c-9a41-2f8d0c3e5b21"],
"emails": [
"anna@example.com",
{ "email": "bo@example.com", "firstname": "Bo", "lastname": "Berg", "phone": "0701234567" }
]
}'
| Field | Type | Required | Description |
|---|---|---|---|
uuids | string[] | Yes | The lists, 1 to 50 |
emails | array | Yes | 1 to 1000 entries. Each entry is an email address as a string (max 128 characters) or a contact object, and the two forms can be mixed |
No other fields are accepted. Every entry is put on every list in uuids.
A contact object has email and any of the fields below. The fields are only used
when a new contact is created for the address - an existing contact is never changed.
An unknown field is refused with 400.
| Field | Max length | Description |
|---|---|---|
email | 128 | Required |
firstname | 128 | First name |
lastname | 128 | Last name |
phone | 128 | Phone number |
in_personnr | 20 | Personnummer or samordningsnummer. A valid one is stored formatted and also sets dob |
dob | - | Date of birth, YYYY-MM-DD |
address | 128 | Street address |
co | 128 | c/o |
zipcode | 16 | Postal code |
post | 128 | Postal town |
country | 8 | Country |
cname | 128 | Company name |
org_number | 128 | Organisation number |
Response - 200 OK:
{
"lists": [
{
"uuid": "9c1b2f4e-77aa-4f0c-9a41-2f8d0c3e5b21",
"subscribed": 1,
"resubscribed": 0,
"already_subscribed": 1
}
],
"created": 1
}
lists- one entry per list, in the order ofuuids.subscribed- addresses put on the list.resubscribed- addresses that had unsubscribed and were put back on the list.already_subscribed- addresses already on the list, left as they were.
created- new subscriber contacts created for addresses that had no contact.
What happens on the Memlist side, per list:
- The address is matched to a contact the list is offered to - in the list's company or a company below it - that the API user has access to. That contact is subscribed.
- An address without such a contact becomes a new subscriber contact (no membership) in the list's company, created with the fields of its entry.
- An existing contact is left as it is, whatever fields its entry carries.
- A contact who had unsubscribed from the list is put back on it.
- Addresses compare case-insensitively and each counts once - the first entry of an address is the one used.
- Calling it again with the same input changes nothing, so a call can safely be retried.
Errors:
| Status | Body | When |
|---|---|---|
400 | validation payload | Missing or empty arrays, too many entries, an invalid uuid, email or dob, a contact object without email, or an unknown field |
401 / 422 | The token is missing, invalid or expired - request a new one | |
404 | { "reason": "list not found", "uuids": ["..."] } | A list does not exist or belongs to a company the API user cannot access. Nothing is written, not even for the other lists |
429 | More than 30 calls per minute |
Example: a minimal custom form
const HOST = 'https://<instance>.memlist.se';
const LIST_UUID = '9c1b2f4e-77aa-4f0c-9a41-2f8d0c3e5b21';
// 1. Fetch the list info and render your form from it.
const info = await fetch(`${HOST}/api/v3/subscription_list/public/${LIST_UUID}`)
.then(r => r.json());
// 2. Subscribe on submit.
async function subscribe(form_values) {
const res = await fetch(`${HOST}/api/v3/subscription_list/public/${LIST_UUID}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email: form_values.email,
firstname: form_values.firstname,
lastname: form_values.lastname,
terms_accepted: form_values.terms_accepted === true
})
});
if (res.status === 201) return true;
const err = await res.json().catch(() => ({}));
throw new Error(err.reason || `subscribe failed: ${res.status}`);
}