Organizations & Members
Invite users, manage membership roles, and control who has access to your org.
All endpoints require an org_admin role unless noted otherwise.
ℹ️
All org management endpoints require authentication (access_token cookie)
and org_admin membership unless stated. Non-admin members can only call
GET /org and GET /org/members.
Get organization
GET
/org
Response (200)
{
"status": "success",
"data": {
"org": {
"id": "org_01ABCD",
"name": "Acme Corp",
"slug": "acme-corp",
"plan": "pro",
"status": "active",
"appUrl": "https://app.acme.com",
"createdAt": "2024-11-01T09:00:00.000Z"
}
}
}
List members
GET
/org/members
Response (200)
{
"status": "success",
"data": {
"members": [
{
"userId": "usr_01HXYZ",
"name": "Alice Chen",
"email": "alice@acme.com",
"role": "org_admin",
"status": "active",
"joinedAt": "2024-11-01T09:00:00.000Z",
"metadata": {}
},
{
"userId": "usr_01HABC",
"name": "Bob Smith",
"email": "bob@acme.com",
"role": "member",
"status": "active",
"joinedAt": "2025-01-10T14:30:00.000Z",
"metadata": { "appRole": "editor", "team": "engineering" }
}
]
}
}
Invite a member
Sends an invitation email to the address. The invite link is valid for 7 days.
If an appUrl is set on the org, the invite email includes a prominent
link to your application so new members know where to go after joining.
POST
/org/members/invite
Request body
| Field | Type | | Description |
| email | string | required | Email address to invite. |
| role | string | optional | "member" (default) or "org_admin". |
Request
{ "email": "bob@acme.com", "role": "member" }
Response (201)
{
"status": "success",
"data": {
"invite": {
"id": "inv_01XYZ",
"email": "bob@acme.com",
"role": "member",
"expiresAt": "2025-06-01T00:00:00.000Z"
}
}
}
Error responses
| Status | Cause |
| 409 | Email is already a member or has a pending invite. |
| 403 | Caller is not an org_admin. |
List pending invites
GET
/org/invites
Response (200)
{
"status": "success",
"data": {
"invites": [
{
"id": "inv_01XYZ",
"email": "bob@acme.com",
"role": "member",
"invitedBy": "alice@acme.com",
"expiresAt": "2025-06-01T00:00:00.000Z"
}
]
}
}
Accept an invite
The invited user calls this endpoint with the token from their email link.
If the user does not have an account, they are created. On success, a full
auth session is established (access + refresh token cookies set).
POST
/org/invites/:token/accept
Path parameters
| Parameter | Description |
| token | The invite token from the email link. |
Request body (new users only)
| Field | Type | | Description |
| name | string | required for new users | Display name for the new account. |
| password | string | required for new users | Password for the new account (min 8 chars). |
Response (200)
{
"status": "success",
"data": {
"accessToken": "eyJhbGciOiJIUzI1NiIs...",
"refreshToken": "eyJhbGciOiJIUzI1NiIs...",
"user": { "id": "usr_01HABC", "name": "Bob Smith", "email": "bob@acme.com" },
"membership": { "role": "member", "status": "active", "metadata": {} },
"org": { "name": "Acme Corp", "slug": "acme-corp", "appUrl": "https://app.acme.com" }
}
}
Error responses
| Status | Cause |
| 400 | Token is invalid or expired. |
| 409 | Email already belongs to an existing account (send login instead of registration fields). |
Store arbitrary key-value data on a membership. Use this to carry your own roles,
departments, or permission flags — they are returned in every
POST /public/verify response
inside membership.metadata, so your backend can make authorization decisions
without a second API call.
PATCH
/org/members/:userId
Path parameters
| Parameter | Description |
| userId | ID of the member to update. |
Request body
| Field | Type | | Description |
| metadata |
object |
required |
Free-form JSON object. The entire object is replaced on each call — include all keys you
want to keep, not just the ones you are changing.
|
Request
{
"metadata": {
"appRole": "editor",
"team": "engineering",
"permissions": ["read", "write"]
}
}
Response (200)
{
"status": "success",
"data": {
"membership": {
"id": "mem_01HABC",
"userId": "usr_01HABC",
"role": "member",
"status": "active",
"metadata": { "appRole": "editor", "team": "engineering", "permissions": ["read", "write"] }
}
}
}
ℹ️
AuthWorx stores and forwards metadata — it does not interpret or enforce it.
Your application owns the semantics. This keeps AuthWorx focused on identity while your app
handles authorization.
Example — reading metadata in your app
Node.js — verify + RBAC check
const { data } = await authworxVerifyToken(token);
if (!data.valid || data.membership?.status !== 'active') {
return Response.json({ error: 'Unauthorized' }, { status: 401 });
}
// Your RBAC logic — AuthWorx is just the carrier
const appRole = data.membership.metadata?.appRole;
if (appRole !== 'editor') {
return Response.json({ error: 'Forbidden' }, { status: 403 });
}
Error responses
| Status | Cause |
| 400 | metadata is missing or not a JSON object. |
| 403 | Caller is not an org_admin. |
| 404 | User is not a member of this org. |
Remove a member
DELETE
/org/members/:userId
Path parameters
| Parameter | Description |
| userId | ID of the member to remove. |
Response (200)
{ "status": "success", "data": { "message": "Member removed successfully" } }
Error responses
| Status | Cause |
| 400 | Cannot remove yourself or the last admin. |
| 403 | Caller is not an org_admin. |
| 404 | User is not a member of this org. |