Inviting a teammate and managing their role with RBAC
Every rollfuse Organization is a walled garden: nobody joins without an invite, and nobody acts beyond what their role allows. This tutorial covers the full lifecycle: sending an invite, accepting it, and granting or revoking a role once the person is a Member.
How RBAC works here
A Member is the account inside your Organization; a Role is what that Member is allowed to do. Out of the box a Member holds no role until one is granted. Granting the member role takes effect immediately. Granting the admin role is more sensitive, so it does not apply right away: it raises a pending approval that a second, different Member holding the role-management permission must approve before the Member actually becomes an admin. This two-person rule exists specifically so that nobody can quietly promote themselves.
Every one of the calls below requires a session token for a Member who holds the permission to manage members and roles in your Organization. Replace token, orgId and the ids in the examples with your own.
Step 1: send the invite
const orgId = "018f2f3a-6000-7000-9c3a-1f2b3c4d5e6f";
const token = process.env.ROLLFUSE_SESSION_TOKEN;
const inviteRes = await fetch(
`https://api.rollfuse.com/v1/organizations/${orgId}/invites`,
{
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ email: "dev@example.com" }),
}
);
const invite = await inviteRes.json();
console.log(invite.status); // "pending"
The body only carries the email address; the invite is always issued into your own Organization, so there is no field to point it elsewhere. The response is a MemberInviteResponse with the invite's id, its status (pending), an expires_at seven days out, and no token: the accept link with the real token is only ever emailed to the invited address.
If you need to pull the invite back before it is used, revoke it:
await fetch(
`https://api.rollfuse.com/v1/organizations/${orgId}/invites/${invite.id}`,
{ method: "DELETE", headers: { Authorization: `Bearer ${token}` } }
);
Step 2: accept the invite
Accepting is the one call in this flow that is not authenticated: the token embedded in the emailed link is itself the credential. It travels as a query parameter, not in the body.
const acceptRes = await fetch(
`https://api.rollfuse.com/v1/invites/accept?token=${encodeURIComponent(inviteToken)}`,
{ method: "POST" }
);
const accepted = await acceptRes.json();
console.log(accepted.member_id, accepted.organization_id);
This creates the Member record, but with no role attached and no session: the new teammate signs in afterward through your Organization's normal login. Any problem with the token (unknown, expired, revoked, already used) comes back as the same generic error, on purpose, so an attacker probing invite links learns nothing about which case actually happened.
Step 3: grant a role
Once the Member exists, grant them whichever role fits the job. Here we grant member, which is immediate:
const grantRes = await fetch(
`https://api.rollfuse.com/v1/members/${accepted.member_id}/roles`,
{
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ role: "member" }),
}
);
const grant = await grantRes.json();
console.log(grant.status); // "granted"
Granting admin instead comes back with status: "pending_approval": the RoleAssignment is not created until a different admin approves the request (see the audit and approvals tutorial for that step). Granting a role the Member already holds is rejected as a conflict, so it is safe to check the response status rather than assume success.
Step 4: revoke a role
If someone changes teams or leaves, remove the role they no longer need:
await fetch(
`https://api.rollfuse.com/v1/members/${accepted.member_id}/roles/member`,
{ method: "DELETE", headers: { Authorization: `Bearer ${token}` } }
);
Revoking a role the Member does not hold is rejected; revoking one of several roles a Member holds leaves the others untouched, so you can pull back exactly the access you intended and nothing else.