Aller au contenu

Authentication

How to get a token with email and password in the community, the security check (ALTCHA) on sign-in, sign-up and recovery, send it on every call, how long and where it is valid, the public routes, acting on behalf of another member, the personal API key (X-Api-Key) with its per-module scope, IPs and per-minute limit, and how to set up a safe integration.

Cet article n’a pas encore été traduit. Vous lisez la version en anglais.

The Memberfy API accepts two ways in: a JWT token, which you get with an email and password, or a personal API key, created on screen and scoped by module. With the token, you exchange an email and password for it and send it on every call. Sign-in happens in a community, the one in X-CommunityId: the email and password are the person's in that community, and the token is valid only there. What the person can do depends on their role there.

Get the token

Sign-in is meant for a person in a browser: besides email and password, it carries the altcha field, the security check. A server integration does not sign in: use an API key.

curl -s -X POST https://api.memberfy.net/api/auth/login \
  -H "Content-Type: application/json" \
  -H "X-CommunityId: $COMMUNITY_ID" \
  -d '{"email":"[email protected]","password":"your-password","altcha":"'"$ALTCHA_PAYLOAD"'"}'

Response (abridged):

{
  "success": true,
  "message": "Login successful.",
  "data": {
    "token": "eyJhbGciOi…",
    "member": { "id": "…", "communityId": "…", "role": "admin", "email": "[email protected]", "fullName": "Maria Souza", "joinedAt": "…" }
  }
}

Keep data.token. data.member is the session member in this community: id is the member id (the same memberId the other routes use), with communityId, role, displayName, fullName, email, avatarUrl, joinedAt and isSuperAdmin. Sign-up (POST /api/auth/register) returns the same shape.

  • Without X-CommunityId, or with an id that isn't a community: 400, with errors[0].param = "X-CommunityId".
  • Wrong email or password: 401 · Invalid email or password. The response takes the same time whether or not an account with that email exists in the community.
  • Someone in more than one community has a password in each: one sign-in and one token per community.
  • Without altcha, or with an invalid one: 422, with errors[0].param = "altcha" (see below).

Security check

POST /api/auth/login, POST /api/auth/register and POST /api/auth/recovery-password require, on every call, the altcha field: the solution to an ALTCHA proof-of-work challenge, signed by the API. The browser solves it on its own, with no click and no third-party call; the Memberfy app already does this.

  1. Ask for a challenge:
    • to sign in: POST /api/auth/challenge, with X-CommunityId and {"email":"[email protected]"} in the body. The challenge comes with the work that signing in with that email requires right now, which may be more than usual;
    • to sign up and recover the password: GET /api/auth/challenge.
  2. Solve it with the ALTCHA widget or library (altcha-lib). The result is a base64 payload.
  3. Send the payload in the call's altcha field. Each challenge is valid for 5 minutes and works once: to try again, ask for another.
curl -s -X POST https://api.memberfy.net/api/auth/challenge \
  -H "Content-Type: application/json" \
  -H "X-CommunityId: $COMMUNITY_ID" \
  -d '{"email":"[email protected]"}'
# { "success": true, "data": { "parameters": { "algorithm": "PBKDF2/SHA-256", … }, "signature": "…" } }
SituationResponse
altcha missing, invalid, expired or already used422, errors[0].param = "altcha" · Wait for the security check to finish and try again. (and the variants for invalid, expired and already used)
On sign-in, a lighter challenge than the one that email requires right now422, param = "altcha" · This sign-in needs a new security check. Try again. Ask for another challenge with POST /api/auth/challenge and resend
Too many attempts (challenges, sign-ins, sign-ups or recoveries)429 · Too many attempts…, with the Retry-After header in seconds. Wait that long before trying again
The check is unavailable right now503, param = "altcha". Try again in a moment

A wrong password is still 401, and the right password gets in: the account is not locked by wrong attempts. The account owner gets a security email, at most one a day, when there are many attempts with the wrong password.

Use the token

On every call that isn't public:

Authorization: Bearer <token>

Just the token, after Bearer . Together with the X-CommunityId of the same community you signed in to; without it, the token's community applies.

curl -s https://api.memberfy.net/api/auth/me \
  -H "Authorization: Bearer $TOKEN" \
  -H "X-CommunityId: $COMMUNITY_ID"

GET /api/auth/me returns the session member right in data, in the same shape as the sign-in, plus community, fields and missingRequiredFields. There is no list of communities in this response: the person's communities come from GET /api/members/me/communities.

Lifetime

The token is valid for 7 days. There is no refresh token: when it's close to expiring, sign in again.

SituationResponse
No token on a protected route401 · Authentication token is required
Invalid or expired token401 · Invalid or expired token
Token from another community (X-CommunityId, or the body's communityId, isn't the community of the sign-in)401: sign in to that community
Token issued before the person changed the password, or in an old format401: sign in again
Insufficient role403 · Required role: owner, admin. Your role: member.

These errors come in the standard envelope, with errors[] (the param is Authorization when the token is missing or invalid), and in the language of Accept-Language; without a recognized language, in English.

Public routes

These don't need a token: sign-in, sign-up, password recovery (all three with the security check), its challenges (/api/auth/challenge) and reads of what is Public in the community (public spaces, the pricing page). On many reads the token is optional: without it, you get only what is public; with it, you also get what that person can see.

Acting on behalf of another member

Owners, admins and moderators can send X-MemberId: <member id> to act as another member of the same community (to post on behalf of someone on the team, for example). Rules:

  • only those three roles (403): "You are not allowed to act as another member (X-MemberId).";
  • the member has to be in the same community (404): "Member not found in this community.";
  • nobody acts on behalf of an owner without being an owner;
  • acting as another member doesn't lend you their role: the permissions are still yours.

API key

For an integration (a CRM, n8n, a script) to call the API without signing in, use a personal API key. It is created in Integrations & APIs, is shown only once and acts as the person who owns it, with the scope they checked.

curl -s https://api.memberfy.net/api/members \
  -H "X-Api-Key: $MEMBERFY_API_KEY" \
  -H "X-CommunityId: $COMMUNITY_ID"
  • The key has the format mfk_ + 8 characters + _ + the secret. Lists show only the start (mfk_7Hq2Lx9a…).
  • Always send X-CommunityId with it, with the key's community. Without it: 400, param: "X-CommunityId". With another community (in the header or in the body's communityId): 401.
  • A member's key (outside the team) while Members can create API keys is off: 403. The key is not deleted and works again when the owner turns the option on. Listing or creating your own keys in that state also answers 403, with param: "memberApiKeysEnabled".
  • No Authorization. Key and token on the same call: 400 API_KEY_WITH_AUTHORIZATION.
  • A key that is invalid, revoked, expired, or belongs to someone who left the community: 401.
  • Keys are managed through the Integrations routes (/api/integrations/api-keys), with the sign-in token only: no key creates, edits or revokes a key.

The scope

Each operation belongs to one of the 33 modules and requires a level: read, write (create and edit) or delete (delete and destructive actions). The scope is written <module>:<level>, like members:read or bookings:delete. The reference shows each operation's scope, next to API key.

  • The level follows the verb: GET is read; POST, PUT and PATCH are write; DELETE is delete.
  • Actions that take something offline or undo it require delete even when they are a POST or PUT: cancel, revoke, archive, unpublish, close, stop the live stream, replace a file, decline, withdraw, discard, moderation decisions and the PUTs that replace a person's entire list of tags or fields. The full list is in Integrations & APIs.
  • Levels don't add up: delete doesn't include read.
  • The effective permission is the key's scope and, at the same time, the person's current role. Each route's role check still applies: a member's key with members:write doesn't change anyone's role.
SituationResponse
Route that requires sign-in, without the scope403 API_KEY_SCOPE_MISSING, with module, level and scope in errors[0]
Route that requires sign-in and belongs to no module403 API_KEY_ROUTE_NOT_COVERED
Public route (or optional sign-in), without the scopeThe key is ignored and the call proceeds as anonymous: only what is public comes back
Public route, with the scopeProceeds as the person who owns the key

What no key reaches

With any scope, 403 API_KEY_BLOCKED ("This action does not accept an API key."): SuperAdmin (/api/admin), account and sign-in (/api/auth, including sign-in and password recovery, and changing a member's email and phone), OAuth and MCP, payouts, payout details and KYC, refunds, checkout and saved cards, downsell, add-on purchases (by the member and by the team), plan change (change-tier), Pay now (pay-now), platform access requests, managing the keys themselves (/api/integrations) and the X-MemberId header. The reference marks these operations with Does not accept an API key.

Private areas are also out of reach for any key: the chat and notifications (/api/chat, /api/notifications), browser and phone notifications (/api/push: devices and preferences), Feedback, development plans, Recognition, Time Tracking and Improve with AI (/api/ai).

The grid also has the channels (Channel, /api/channels) and upvotes (Upvote, /api/upvotes) modules; in upvotes, changing the state and merging topics require delete, because they can archive. And the 1:1 (/api/one-on-ones), Performance review (/api/performance-reviews) and Goals & OKRs (/api/goals) stay out of reach for any key, as private areas.

The networking module (Networking, /api/networking) requires delete to reject someone's entry to the program and to decline a session. The workflows module (Automations, /api/workflows) is read-only by key: with workflows:read, the key lists and looks up workflows, versions, run history and deliveries; any write (create, edit, turn on, pause, run, test, cancel, approve, clear) gets 403 API_KEY_BLOCKED with a key and stays in the app and the AI assistant only, which use the person's own sign-in.

Other routes belong to the module of their subject: gamification includes badges (/api/badges); members, sign-up requests (/api/signup-requests, where rejecting requires delete), contact consent (/api/contact-consent) and the member's public page (/api/public/members); campaigns, SMS and WhatsApp (/api/sms, /api/whatsapp); reports, the default emails (/api/email-overrides, next to /api/emails); events, my tickets (/api/my/tickets); and preferences, the SMS unsubscribe link.

Also assigned to a module: scheduled posts (/api/scheduled-posts) to content, with one caveat: publishing a scheduled feed post (/api/scheduled-posts/status/...) also requires feed:write, and the list only includes feed posts for keys with feed:read; member referrals (/api/referrals) to members; points (/api/points) to gamification; and the public community summary (/api/public/community-summary) to community.

Also out of reach for any key, with API_KEY_BLOCKED: the third-party secrets vault (/api/vault, with the AI limits and the data notice), the inbound address of the automations' webhook triggers (/api/workflows/{id}/triggers), the workflow hooks (/api/workflow-hooks) and the SMS provider callbacks (/api/public/sms).

Also blocked, with API_KEY_BLOCKED: the community's third-party accounts: video integrations (/api/video-integrations) and sending through your Resend account (/api/email-provider); privacy requests (/api/privacy: account deletion and data export, only from the person's own sign-in) and the app's own state (/api/menu, the side menu counters, and /api/welcome, the welcome screens).

The path is compared case-insensitively and already normalized (double slashes, trailing slash, %2F): /REFUNDS is blocked like /refunds. A path with a . or .. segment, raw or encoded (%2e), is refused with 400 API_KEY_PATH_INVALID. The same 400 applies to a path where an encoded character changes the route's module, like /api/communities/%70lans (which decoded would read plans). Accents, %20 and %2F inside a parameter (an id, a slug, a search term) still work as usual.

Allowed IPs

The key can have a list of up to 20 IPv4, IPv6 or CIDR ranges. Empty, it accepts any IP. A call from outside the list: 403 API_KEY_IP_NOT_ALLOWED, with the IP in the message; the attempt is recorded on the key (Last refused IP, on screen). The IP that counts is the one of the client reaching Memberfy; headers like X-Forwarded-For sent by the caller don't change the result.

Call limit

120 calls per minute per person in each community, adding up all their keys in that community (the same person's keys in another community count separately). Creating more keys doesn't raise the limit. Above that, 429 API_KEY_RATE_LIMITED, with the Retry-After header in seconds:

curl -si https://api.memberfy.net/api/bookings \
  -H "X-Api-Key: $MEMBERFY_API_KEY" \
  -H "X-CommunityId: $COMMUNITY_ID" | grep -i -E "^HTTP|retry-after"
# HTTP/2 429
# retry-after: 23

Wait the Retry-After seconds before trying again.

What counts as usage

The Usage block in Integrations & APIs counts, per month (UTC), the calls made with a valid key, including those that get a scope 403. Not counted, and not spending the limit: an invalid, revoked or expired key (401), an invalid path (400), a blocked action (API_KEY_BLOCKED), an IP outside the list and the limit refusal itself (429).

In Logs

Every write or deletion made with a key is recorded with its result, "POST /api/status via key n8n", along with creating, renaming, changing the IPs of and revoking keys. A refused write (blocked, IP, limit) shows up at most once per minute per key and reason. Read it with GET /api/activity-logs?source=apiKeys. See Logs › API keys.

Example: create a post from n8n

curl -s -X POST https://api.memberfy.net/api/status \
  -H "X-Api-Key: $MEMBERFY_API_KEY" \
  -H "X-CommunityId: $COMMUNITY_ID" \
  -H "Content-Type: application/json" \
  -d '{"spaceId":"'"$SPACE_ID"'","content":"Registration is open for the November meetup!"}'

It needs feed:write. The post is published with the key's owner as the author. Without the scope:

{
  "success": false,
  "message": "This API key has no create and edit permission on Feed.",
  "errors": [
    { "param": "X-Api-Key", "message": "This API key has no create and edit permission on Feed.", "code": "API_KEY_SCOPE_MISSING", "module": "feed", "level": "write", "scope": "feed:write" }
  ]
}

Integration account

A server integration uses an API key: it has a per-module scope, allowed IPs, is revoked without changing a password and does not go through the sign-in security check. If the integration needs something no key reaches, create a member just for it:

  1. Invite [email protected] with the minimum role the integration needs: Finance to read sales; Admin to create plans and products.
  2. Keep the password in a secrets vault, never in the code.
  3. Sign in at the start of each run, solving the POST /api/auth/challenge challenge with altcha-lib, and reuse the token until it expires (7 days). Do not sign in on every call.
  4. If the token leaks, change the account's password: tokens already issued stop working immediately.

Endpoints

GET /api/auth/challengeSecurity check challenge, for sign-up and password recovery
POST /api/auth/challengeChallenge for signing in with an email ({ email }, with X-CommunityId)
POST /api/auth/loginEmail, password and altcha → token
GET /api/auth/meWho owns the token
POST /api/auth/support-entryTrades the link from Memberfy support's Enter for an owner session, valid only in that community (see SuperAdmin)
POST /api/auth/registerSign-up (fullName, email, password with at least 8 characters, an uppercase letter, a lowercase letter and a number, and altcha)
PUT /api/auth/change-passwordChange the password in this community. The person's other tokens in this community, and the AI assistant connections, stop working; the response carries a new token in data.token, so the session that changed it carries on
POST /api/auth/recovery-passwordRecover the password (email and altcha). The temporary password only opens the community where it was requested
POST /api/auth/set-passwordSet the password from the invite link (id, token, password). Answers 400 for a wrong token and 410 for a used or expired link

Good practices

  • Never put the token in the code of a public website: anyone could read it.
  • Always use HTTPS (https://api.memberfy.net).
  • Handle a 401 by signing in again once; if it fails again, stop and raise an alert.
  • An integration that talks to more than one community keeps one token (or one key) per community.
  • Keep the API key in a secrets vault; if it leaks, revoke it in Integrations & APIs and create another.

AI assistant (MCP) access

The owner turns the assistant on by group of roles. The fields live in the community settings:

FieldWho uses it when true
mcpMembersmembers
mcpStaffmoderator, finance and admin
mcpAdminsadmins only

The owner always can. All start false.

CallWho
GET /api/communities/settingsreads: owner, admin, moderator and finance (member: 403)
PATCH /api/communities/{id}/settings with mcpMembers, mcpStaff, mcpAdminswrites: owner only. Others get 403; a value that is not a boolean gets 400
POST /api/communities/domainreturns mcpAllowed (your role may use the assistant; false when signed out)
GET /api/oauth/authorize/inforeturns mcpAllowed for each community

With your role's switch off, authorization is refused with 403 and /mcp refuses every call, until the owner turns it back on; connections are not deleted.