As a beginner with the REST API, I found it necessary to read code and experiment to figure out how to get things done.
There are a number of implementation choices that are unusual.
There is some documentation in src/mailman/rest/docs. It's oriented toward Python and intermixes the mailman client & REST API, apparently trying to show equivalence.
Here are some notes that may help others to avoid repeating my research. They reflect Mailman 3.1. Some reflect issues that I've opened, but I think it's worth knowing how things work today. I attempted to tie together some of pieces that are in the documentation - and note some things that aren't.
I may have some things wrong, but this reflects my current understanding. (And client code.)
The REST API has a hybrid interface: Requests are made with application/x-www-form-urlencoded POST, PUT, PATCH and delete http requests. Requests are also accepted as parameter strings.
The responses are JSON. (This is rather surprising - one would expect JSON requests - and I hope someday they'll be accepted, as the split complicates clients. I suspect this evolved to simplify the initial Web GUI client (Postorious), but it precludes using standard JSON-in/JSON-out client libraries.)
Error responses are often misidentified as content-type: application/json, but actually contain a text/plain error message. This isn't universally true; for example the 401 response actually IS JSON. A client has to guess and handle decoding exceptions.
The API presents resources hierarchically, rooted at '/'. The top level resources include /users, /lists. /addresses, /domains, /system
The next level is a resource id. There is a an id which is stable for the lifetime of an object, and a current name (which can be changed). For example, a list has a name like mylist@example.com, and a list_id of mylist.example.com. But if you change the list name, it becomes mynewname@example.net, while the list_id remains mylist.example.com.
Lists are associated with "domains", which are the "domain part" of the list's address. That is, the part after the @. This is sometimes referred to as the "mail host", but there need not be a real host. Just something on the left side of a DNS MX record. Also, don't confuse the use of "mail host" with that of "SMTP Server", which is how Mailman sends mail. The domain is where Mailman expects to receive it. A fully qualified list name is the list name + domain - e.g. its posting address. One domain can (and usually does) support more than one list. If you delete the domain, all its lists go away too.
A domain has to be created before you can create a list with an address in that domain.
Resources are created with a POST to their top-level resource. To create a domain, post to /domains with mail_host => the domain, and (optionally) description => a description for the GUI. The response isn't JSON as one would expect. In fact, it's an empty application/json response with a 201 status.
To create a list, one POSTs to /lists. This post takes a restricted set of parameters; in the case of a list, just its fqdn_listname, (and an optional style - which isn't well defined). The response isn't JSON as one would expect. The Location header of the response contains a URL of the new list.
To configure the list, you have to follow up with a PUT or PATCH to that URL + /config This is where you can set the description, posting policy, etc. It's unrealistic to do a PUT; even if you're cloning another list, there's no programatic way to determine which attributes are writable. PATCH what you know...
Members are associated with e-mail addresses - which belong to Users. You create a user by posting to /users with email => the email address, and optionally display_name => the name string. (A user can also be implicitly created by subscribing an e-mail address, but that gets confusing.) The e-mail address is the primary email address for the user. More later. Again, you get a Location header in the response, which you can use to PATCH /preferences to set delivery_status, etc. .These preferences are part of a hierarchy - many have a system default, a list default, the user default, and a subscription to that list value. You can find the User associated with a list by a GET of /addresses/address@example.net. This GET returns two URLs: user => the user owning this address, and self_link => the address object. Note also that there are both an email and original_email attribute. The latter preserves case. The former is used internally by Mailman as a resource key, etc. (Though exactly what happens if John@example.net and john@example.net both subscribe is unclear.)
Once a list is configured, you can add members. This requires the list_id - which you don't (officially) have. So you do a GET on the list resource, to get the list_id. Then you can subscribe a member with list_id => (the list id), subscriber => email address. Optionally you can pre_verify/confirm/approve the member and/or add a display_name.. POST to /members. Again, you get a Location header back. You can't specify everything you might like. as a creation attribute. So you may have to PATCH the member to, for example, set the moderation_status.
You may also need to PATCH the member /preferences if you want to set list-level delivery status, etc.
Consider adding at least an owner when creating a list.
One challenge is that almost everything requires multiple REST operations to set up. But REST (by definition) is stateless. So the best you can do is order operations & hope. The mailman client examples refer to transactions (e.g in users.rst, there is 'transaction.commit(); - but REST can't hold state. It does appear that the server uses DB transactions to ensure that any given REST operation is ACID, but the composite operations (e.g. create a list and set it's config) can not be Atomic. This is an architectural flaw in the API.
Symbolic names (which are required) for attributes are in the src/mailman/interfaces/(class).py.
What you can get/set comes from src/mailman/rest/(class).py.