Hoppa till huvudinnehåll

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:

  1. Link to the hosted form - every list has a ready-made public page. No code required.
  2. 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.

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). email is always required.
  • term - an optional consent text configured on the list, or null. If present, show it with a checkbox and send terms_accepted: true when the visitor accepts.
  • questions - optional checkboxes configured on the list. Send the ids the visitor ticked as answered_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
}'
FieldTypeRequiredDescription
emailstringYesMax 254 characters
firstnamestringNoMax 128 characters
lastnamestringNoMax 128 characters
phonestringNoMax 64 characters
answered_prop_idsnumber[]NoIds of ticked questions from the list's questions
terms_acceptedbooleanNoSend 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: true was sent, the acceptance is recorded on the member under Samtycken.

Errors:

StatusBodyWhen
400validation payloadMissing 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​

EndpointsLimit
Public GET endpoints120 requests per minute per IP
Public POST endpoints20 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.

MethodPathPurpose
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_listCreate 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}/subscribersList 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/subscribersPut 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" }
]
}'
FieldTypeRequiredDescription
uuidsstring[]YesThe lists, 1 to 50
emailsarrayYes1 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.

FieldMax lengthDescription
email128Required
firstname128First name
lastname128Last name
phone128Phone number
in_personnr20Personnummer or samordningsnummer. A valid one is stored formatted and also sets dob
dob-Date of birth, YYYY-MM-DD
address128Street address
co128c/o
zipcode16Postal code
post128Postal town
country8Country
cname128Company name
org_number128Organisation 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 of uuids.
    • 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:

StatusBodyWhen
400validation payloadMissing or empty arrays, too many entries, an invalid uuid, email or dob, a contact object without email, or an unknown field
401 / 422The 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
429More 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}`);
}