A site member is a person who signed up to the site and can log in to its members area. Every member is also a contact, but most contacts are not members. To find a person who may not be a member, search contacts instead, as in Find Contacts: POST https://www.wixapis.com/contacts/v5/contacts/search with {"search": {"search": {"expression": "Jane Doe", "mode": "AND"}}}; each result in contacts[] has memberInfo.memberId only when the contact is a member.
All calls go to the Members API. Every request below is complete and was run on a live site: send it as written, with your own values, and do not look up the method schema first. The steps already give each field these calls need: the fieldsets value (FULL), the filter fields and operators (Step 2), the response fields (Step 3), the empty {} body of Approve and Block, and the member.profile.nickname body of Update Member, which needs no id, revision or field mask (Step 4). Reading is safe. Approving, blocking, adding and renaming change the site: Step 4 says when to ask the user first.
Members calls return the PUBLIC fieldset by default. It has no loginEmail, and it returns status as UNKNOWN. Send "fieldsets": ["FULL"] in a query body, or fieldsets=FULL in a URL, whenever the answer needs the email or the status.
POST https://www.wixapis.com/members/v1/members/query
| The user gives | Query Members body |
|---|---|
| A login email | {"query": {"filter": {"loginEmail": "jane@example.com"}}, "fieldsets": ["FULL"]} |
| A first and last name | {"query": {"filter": {"contact.firstName": "Jane", "contact.lastName": "Doe"}}, "fieldsets": ["FULL"]} |
| A name shown on the site ("Syam K") | {"query": {"filter": {"profile.nickname": "Syam K"}}, "fieldsets": ["FULL"]} |
| A partial name ("syam") | {"search": {"expression": "syam", "fields": ["contact.firstName", "contact.lastName", "profile.nickname"]}, "fieldsets": ["FULL"]} |
| Pending, approved or blocked members | {"query": {"filter": {"status": "PENDING"}}, "fieldsets": ["FULL"]} |
| Members who joined in a period | {"query": {"filter": {"$and": [{"createdDate": {"$gte": "2026-10-01T00:00:00Z"}}, {"createdDate": {"$lt": "2026-11-01T00:00:00Z"}}]}}, "fieldsets": ["FULL"]}. One object with both $gte and $lt is rejected with 400 |
| A contact ID | {"query": {"filter": {"contactId": "<contact id>"}}, "fieldsets": ["FULL"]} |
The filter fields. Query Members filters on these fields, with these operators:
| Field | Operators |
|---|---|
loginEmail, contact.firstName, contact.lastName, profile.nickname, profile.slug, contactId, id | $eq, $in, $startsWith |
status (PENDING, APPROVED, BLOCKED, OFFLINE) | $eq, $ne, $in |
createdDate | $gte, $lt, each in its own condition under $and (not in the method schema's list, but it works) |
The method schema also lists $exists, but it is rejected with 400 filter parser Unknown operator $exists. A field that is not listed here is rejected with 400 filter parser Unknown token.
Free text (search.fields) accepts loginEmail, contact.firstName, contact.lastName, profile.nickname, profile.slug and profile.title, at most 4 of them.
One member by email or name, with the fallback, as one flow:
Send the filter:
For a name, send {"query": {"filter": {"contact.firstName": "Sam", "contact.lastName": "Ortiz"}}, "fieldsets": ["FULL"]} instead.
A match comes back as:
When members is empty, send the every-member query (below) and keep the member whose loginEmail, contact.firstName and contact.lastName, or profile.nickname equals the user's words, ignoring case. Do both steps before you answer.
Use the member's id for the calls in Step 4.
How these fields behave:
{"profile.nickname": "Syam K"}. A nested object such as {"profile": {"nickname": "Syam K"}} is rejected with 400 filter parser Unknown token profile.$startsWith also works on them.profile.nickname unless you send fields, so always send fields. It ignores case and matches the start of a word from 3 letters ("Noe" finds Noel, "No" does not). Any one word is enough: "Syam Nobody" returns Syam Testkumar. So keep only the members whose first name, last name or nickname starts with each word the user gave, ignoring case. For a word shorter than 3 letters, read every member (below) and match in code. Do not search for an email as free text: "jane@example.com" also returns every other member at example.com. Filter on loginEmail instead.loginEmail, name or profile.nickname, while a query without a filter returned them at once. So when a filter or a search finds nothing, read every member (below) and match the email or name in code, ignoring case, before you tell the user there is no such member."sorting": [{"fieldName": "createdDate", "order": "DESC"}] inside query (the key is sorting, not sort). List Members rejects a createdDate sort with 400 INVALID_SORT_FIELD.Every member. Send the query with no filter and page with offset:
paging.limit is at most 100. metadata.total is the number of members that match; to only count them, send "paging": {"limit": 1} and read metadata.total. Increase offset by limit until you have read metadata.total members. GET https://www.wixapis.com/members/v1/members?fieldsets=FULL&paging.limit=100&paging.offset=0 (List Members) returns the same list.
The response holds members[] and metadata (count, offset, total). Each member has:
| Field | What it holds |
|---|---|
id | Member ID. Other APIs that work on members take it, for example a blog post's memberId; for a post author, follow Part 0 of How to Create Blog Posts instead |
contactId | The member's contact, for Update a Contact. Do not assume it equals id |
loginEmail | The email the member logs in with (FULL or EXTENDED only) |
status | PENDING (waiting for approval), APPROVED (approved: can log in once the member has a password), BLOCKED (cannot log in), OFFLINE (a blog guest writer who cannot log in). UNKNOWN means the fieldset was PUBLIC |
contact.firstName, contact.lastName | Name |
profile.nickname | The name the site shows for the member, such as the author name on blog posts |
profile.slug | The member's profile page path |
createdDate | When the member joined |
lastLoginDate | Last login, when there is one |
privacyStatus | PRIVATE or PUBLIC profile |
Read one member by ID with GET https://www.wixapis.com/members/v1/members/<memberId>?fieldsets=FULL; it returns {"member": {...}}.
These four calls are complete as shown; send them without a schema lookup. Each returns the changed member in member. Read member.status or member.profile.nickname from the response to confirm; no second read is needed.
A request that names the member and the action ("approve Jane Doe", "block jane@example.com") is the user's confirmation: find the member (Step 2), then make the call. Ask first when the user did not name the action, or when more than one member matches.
Approve a pending member, or unblock a blocked one:
Block a member (they can no longer log in):
To unblock, use Approve: on a BLOCKED member it returns the member with status: "APPROVED" (tested on a live site). Both calls succeed when the member already has the target status, so you can repeat them safely.
Change the name the site shows (for example a blog author name):
The member ID goes in the URL; the body needs no id, revision or field mask. Only the fields you send change; contact.firstName, contact.lastName and loginEmail stay as they were. The response is the whole updated member, with loginEmail and status.
Add a member:
The new member has status: "APPROVED", and profile.nickname is the contact name unless you send member.profile.nickname. It cannot log in until it sets a password; Send Set Password Email emails it a link, so send that only when the user asks. When adding several members, wait about one second between calls to stay under the rate limit.
Last updated: 4 November 2026