Summary and recommendation
Mailchimp's Marketing API (v3.0) manages audience members - subscribers, contacts, tags, and merge fields - not account-level team seats. There is no API endpoint for adding, removing, or listing Mailchimp account users; that surface is UI-only.
Authentication is either HTTP Basic Auth with an API key (full account access, no scope restriction) or OAuth 2.0 (token inherits the authorizing user's permissions). The data center prefix is account-specific and must be resolved from the API key suffix or the OAuth metadata endpoint before constructing any request URL.
When building an identity graph across your SaaS stack, note that Mailchimp's API exposes subscriber identity data - email, merge fields, tags, engagement stats, opt timestamps - but provides no programmatic hook into account-level IAM.
API quick reference
| Has user API | Yes |
| Auth method | API Key (HTTP Basic Auth with 'anystring:<apikey>') or OAuth 2.0 |
| Base URL | Official docs |
| SCIM available | No |
Authentication
Auth method: API Key (HTTP Basic Auth with 'anystring:
Setup steps
- API Key: Log in to Mailchimp → Account → Extras → API Keys → Create A Key. Use as password in HTTP Basic Auth with any string as username (e.g., 'key:
'). - OAuth 2.0: Register an app at https://admin.mailchimp.com/account/oauth2/ to obtain client_id and client_secret.
- OAuth 2.0: Redirect user to https://login.mailchimp.com/oauth2/authorize?response_type=code&client_id=
&redirect_uri= . - OAuth 2.0: Exchange authorization code for access token via POST to https://login.mailchimp.com/oauth2/token.
- OAuth 2.0: Call GET https://login.mailchimp.com/oauth2/metadata to retrieve the user's data center (dc) prefix for constructing the base URL.
- Use the dc prefix (e.g., 'us1') to build all API calls: https://us1.api.mailchimp.com/3.0/...
Required scopes
| Scope | Description | Required for |
|---|---|---|
| N/A (API Key) | API keys grant full account access; no granular scope selection available for API keys. | All operations when using API key auth |
| OAuth 2.0 (no named scopes) | Mailchimp OAuth 2.0 does not expose granular named scopes; the granted token has access equivalent to the authorizing user's account permissions. | All operations when using OAuth 2.0 |
User object / data model
| Field | Type | Description | On create | On update | Notes |
|---|---|---|---|---|---|
| id | string | MD5 hash of the lowercase version of the list member's email address | auto-generated | immutable | Used as the path parameter for member endpoints |
| email_address | string | The member's email address | required | optional | Must be unique per list |
| status | string (enum) | Subscription status: subscribed, unsubscribed, cleaned, pending, transactional | required | optional | Use 'pending' to trigger double opt-in |
| merge_fields | object | Key-value pairs for merge tags (e.g., FNAME, LNAME, PHONE) | optional | optional | Merge tag names are list-specific and configurable |
| interests | object | Map of interest category IDs to boolean values indicating group membership | optional | optional | Interest IDs must be retrieved from the list's interest categories endpoint |
| language | string | ISO 639-1 language code for the subscriber | optional | optional | |
| vip | boolean | Whether the member is marked as VIP | optional | optional | |
| location | object | Subscriber location data: latitude, longitude, gmtoff, dstoff, country_code, timezone | optional | optional | Geo data may be auto-populated by Mailchimp from email open data |
| tags | array | Array of tag objects ({id, name}) associated with the member | optional | managed via separate /tags endpoint | Tags are managed via POST /lists/{list_id}/members/{subscriber_hash}/tags |
| timestamp_signup | string (ISO 8601) | Date and time the subscriber signed up | optional | read-only after set | |
| timestamp_opt | string (ISO 8601) | Date and time the subscriber confirmed opt-in | read-only | read-only | Set automatically by Mailchimp |
| last_changed | string (ISO 8601) | Date and time of the last change to the member record | read-only | read-only | |
| email_type | string (enum) | Email format preference: html or text | optional | optional | |
| stats | object | Engagement stats: avg_open_rate, avg_click_rate | read-only | read-only | |
| source | string | How the member was added (e.g., 'API', 'Admin Add', 'Import') | read-only | read-only | |
| unique_email_id | string | A unique identifier for the email address across all Mailchimp lists | read-only | read-only | |
| web_id | integer | The ID used in the Mailchimp web application | read-only | read-only | |
| unsubscribe_reason | string | Reason provided when a member unsubscribes | read-only | read-only |
Core endpoints
List all members in an audience
- Method: GET
- URL:
https://<dc>.api.mailchimp.com/3.0/lists/{list_id}/members - Watch out for: Max count per request is 1000. Use offset pagination for large lists. Response includes only fields requested via 'fields' param if specified.
Request example
GET /3.0/lists/abc123/members?count=100&offset=0
Authorization: Basic base64(anystring:apikey)
Response example
{
"members": [{"id":"md5hash","email_address":"user@example.com","status":"subscribed"}],
"total_items": 500
}
Get a specific member
- Method: GET
- URL:
https://<dc>.api.mailchimp.com/3.0/lists/{list_id}/members/{subscriber_hash} - Watch out for: subscriber_hash must be the MD5 hash of the lowercased email address, not the raw email.
Request example
GET /3.0/lists/abc123/members/md5(lowercase(email))
Authorization: Basic base64(anystring:apikey)
Response example
{
"id": "8a6f2e3b...",
"email_address": "user@example.com",
"status": "subscribed",
"merge_fields": {"FNAME":"Jane","LNAME":"Doe"}
}
Add or update a member (upsert)
- Method: PUT
- URL:
https://<dc>.api.mailchimp.com/3.0/lists/{list_id}/members/{subscriber_hash} - Watch out for: Use 'status_if_new' (not 'status') to avoid accidentally resubscribing unsubscribed members on update. PUT replaces the full record; use PATCH for partial updates.
Request example
PUT /3.0/lists/abc123/members/md5hash
{
"email_address": "user@example.com",
"status_if_new": "subscribed",
"merge_fields": {"FNAME":"Jane"}
}
Response example
{
"id": "8a6f2e3b...",
"email_address": "user@example.com",
"status": "subscribed"
}
Update a member (partial)
- Method: PATCH
- URL:
https://<dc>.api.mailchimp.com/3.0/lists/{list_id}/members/{subscriber_hash} - Watch out for: PATCH only updates provided fields. merge_fields object is merged at the key level, not replaced entirely.
Request example
PATCH /3.0/lists/abc123/members/md5hash
{
"merge_fields": {"FNAME":"Janet"},
"language": "en"
}
Response example
{
"id": "8a6f2e3b...",
"email_address": "user@example.com",
"status": "subscribed",
"merge_fields": {"FNAME":"Janet"}
}
Add a new member
- Method: POST
- URL:
https://<dc>.api.mailchimp.com/3.0/lists/{list_id}/members - Watch out for: Returns HTTP 400 if the email already exists in the list. Use PUT for upsert behavior instead.
Request example
POST /3.0/lists/abc123/members
{
"email_address": "new@example.com",
"status": "subscribed",
"merge_fields": {"FNAME":"John","LNAME":"Smith"}
}
Response example
{
"id": "a1b2c3d4...",
"email_address": "new@example.com",
"status": "subscribed"
}
Archive (soft-delete) a member
- Method: DELETE
- URL:
https://<dc>.api.mailchimp.com/3.0/lists/{list_id}/members/{subscriber_hash} - Watch out for: DELETE archives the member (removes from active count) but does not permanently delete. To permanently delete, use POST /lists/{list_id}/members/{subscriber_hash}/actions/delete-permanent.
Request example
DELETE /3.0/lists/abc123/members/md5hash
Authorization: Basic base64(anystring:apikey)
Response example
HTTP 204 No Content
Batch subscribe/unsubscribe members
- Method: POST
- URL:
https://<dc>.api.mailchimp.com/3.0/lists/{list_id} - Watch out for: Limited to 500 members per request. 'update_existing: true' is required to update existing members; otherwise existing emails are silently skipped.
Request example
POST /3.0/lists/abc123
{
"members": [{"email_address":"a@x.com","status":"subscribed"},{"email_address":"b@x.com","status":"unsubscribed"}],
"update_existing": true
}
Response example
{
"new_members": [...],
"updated_members": [...],
"errors": [],
"total_created": 1,
"total_updated": 1
}
Update member tags
- Method: POST
- URL:
https://<dc>.api.mailchimp.com/3.0/lists/{list_id}/members/{subscriber_hash}/tags - Watch out for: Tags are created automatically if they don't exist. Set status to 'inactive' to remove a tag. There is no GET endpoint to list all account-level tags directly from this path.
Request example
POST /3.0/lists/abc123/members/md5hash/tags
{
"tags": [{"name":"VIP","status":"active"},{"name":"OldTag","status":"inactive"}]
}
Response example
HTTP 204 No Content
Rate limits, pagination, and events
- Rate limits: Mailchimp enforces a concurrent connection limit per API key rather than a per-minute request cap. The default is 10 simultaneous connections. Batch operations are recommended for bulk work.
- Rate-limit headers: No
- Retry-After header: No
- Rate-limit notes: HTTP 429 is returned when the concurrent limit is exceeded. No standard Retry-After header is documented. Batch endpoint (POST /3.0/batches) is recommended to reduce concurrent connection usage.
- Pagination method: offset
- Default page size: 10
- Max page size: 1000
- Pagination pointer: offset and count
| Plan | Limit | Concurrent |
|---|---|---|
| All paid plans | 10 concurrent connections per API key | 10 |
| Free plan | 10 concurrent connections per API key | 10 |
- Webhooks available: Yes
- Webhook notes: Mailchimp supports list-level webhooks that fire on subscriber events. Webhooks are configured per audience (list) via the API or the Mailchimp dashboard. Mailchimp sends an HTTP POST to the configured URL.
- Alternative event strategy: Mailchimp Transactional (Mandrill) has its own separate webhook system for transactional email events.
- Webhook events: subscribe, unsubscribe, profile, cleaned, upemail, campaign
SCIM API status
- SCIM available: No
- SCIM version: Not documented
- Plan required: Not documented
- Endpoint: Not documented
Limitations:
- Mailchimp does not offer a native SCIM 2.0 endpoint as of the current documentation review.
- SCIM-like provisioning is available only via third-party IdP integrations (Okta, OneLogin, miniOrange) using those providers' Mailchimp app connectors, which map to Mailchimp's Marketing API under the hood.
- The context reference to 'Enterprise' SCIM may reflect third-party connector availability rather than a native Mailchimp SCIM endpoint.
- SSO (SAML) is available via third-party providers but is not natively built into Mailchimp's platform.
Common scenarios
Three integration patterns cover the majority of production use cases. For CRM-to-audience sync, use PUT /3.
0/lists/{list_id}/members/{subscriber_hash} with status_if_new rather than status - using status on a PUT will silently overwrite the status of already-unsubscribed contacts, creating CAN-SPAM and GDPR exposure.
For bulk operations exceeding 500 records, bypass the batch list endpoint and use the async Batch Operations API (POST /3. 0/batches) instead; always inspect the errors array in the response.
For event-driven subscriber intake, register list-level webhooks via POST /3. 0/lists/{list_id}/webhooks - payloads arrive as form-encoded POST bodies, not JSON, and the registration handshake requires the endpoint to return HTTP 200 to an initial GET verification request.
Sync CRM contacts to a Mailchimp audience
- Retrieve the target list_id via GET /3.0/lists.
- For each contact, compute subscriber_hash = MD5(lowercase(email)).
- Use PUT /3.0/lists/{list_id}/members/{subscriber_hash} with 'status_if_new': 'subscribed' and relevant merge_fields.
- For batches > 1, prefer POST /3.0/lists/{list_id} (batch endpoint, max 500/call) or POST /3.0/batches for async bulk operations.
- Check the 'errors' array in the batch response and log any failed records for retry.
Watch out for: Using 'status' instead of 'status_if_new' in PUT will overwrite the status of already-unsubscribed contacts, violating CAN-SPAM/GDPR compliance.
Unsubscribe a user and record the reason
- Compute subscriber_hash = MD5(lowercase(email)).
- PATCH /3.0/lists/{list_id}/members/{subscriber_hash} with body: {"status": "unsubscribed"}.
- Optionally store the unsubscribe reason in a custom merge field via the same PATCH call.
- Confirm the update by checking the returned 'status' field in the response.
Watch out for: Mailchimp does not expose a free-text 'unsubscribe_reason' write field via the API; the field is read-only and populated only when the user self-unsubscribes via Mailchimp's own forms.
Listen for new subscribers via webhook
- Create a publicly accessible HTTPS endpoint that accepts POST requests.
- Register the webhook via POST /3.0/lists/{list_id}/webhooks with 'events': {'subscribe': true} and 'sources': {'user': true, 'api': true, 'admin': true}.
- Mailchimp will send a form-encoded POST to the URL on each subscribe event.
- Parse the 'data[email]', 'data[merges]', and 'data[list_id]' fields from the incoming payload.
- Respond with HTTP 200 within the timeout window to acknowledge receipt.
Watch out for: Mailchimp webhook payloads are form-encoded (application/x-www-form-urlencoded), not JSON. Mailchimp also sends a GET request to the webhook URL during registration to verify it is reachable - the endpoint must return HTTP 200 to GET as well.
Why building this yourself is a trap
Several API behaviors are non-obvious and cause silent data corruption if missed. The subscriber_hash parameter is the MD5 hash of the lowercased email address - not the raw email and not a Mailchimp-assigned UUID; passing the wrong value returns a 404 with no helpful error context.
DELETE /members/{hash} archives the contact rather than permanently deleting it; permanent deletion requires a separate POST to /actions/delete-permanent and is irreversible. Rate limiting is enforced as a concurrent connection cap (default 10 per API key), not a per-minute request rate - sequential queuing avoids 429s more reliably than backoff alone.
API keys carry full account-level access with no scope restriction; there is no mechanism to issue a read-only or audience-scoped key, which is a meaningful least-privilege gap for any integration that only needs read access.
Provisioning automation at scale requires a third-party IdP connector or a platform that wraps the Marketing API with broader identity graph coverage across your SaaS environment.
Automate Mailchimp workflows without one-off scripts
Stitchflow builds and maintains end-to-end IT automation across your SaaS stack, including apps without APIs. Built for exactly how your company works, with human approvals where they matter.