HelloJohn / docs
Organizations

Invitations

Send, manage, and accept invitations to join organizations in HelloJohn.

Invitations

Invitations let you add external users to an organization via email. HelloJohn handles token generation, email delivery, and acceptance — including creating the user account if it doesn't exist yet.

How Invitations Work

1. Admin sends invitation → HelloJohn emails the invitee
2. Invitee clicks the link → Redirected to your app with ?token=...
3. Your app calls POST /v1/invitations/:token/accept
4. If user doesn't exist → Account is created automatically
5. User is added to the organization with the specified role

Sending an Invitation

Via API

curl -X POST "https://api.hellojohn.dev/v1/organizations/org_01HABCDEF777666/invitations" \
  -H "Authorization: Bearer sk_live_abc123" \
  -H "X-Tenant-ID: tnt_01HABCDEF654321" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "bob@example.com",
    "role": "member",
    "redirect_url": "https://app.example.com/accept-invitation"
  }'

Body:

FieldTypeRequiredDescription
emailstringInvitee email address
rolestringmember, admin, or owner
redirect_urlstringWhere to send the invitee after clicking the link
expires_instringInvitation expiry (default: 7d)

Response: 201 Created

{
  "id": "inv_01HABCDEF555999",
  "organization_id": "org_01HABCDEF777666",
  "email": "bob@example.com",
  "role": "member",
  "status": "pending",
  "token": "inv_tok_abc123...",
  "expires_at": "2024-01-22T10:00:00Z",
  "created_at": "2024-01-15T10:00:00Z"
}

Via SDK (React)

import { useOrganization } from "@hellojohn/react";

function InviteMember() {
  const { invitations } = useOrganization();

  const handleInvite = async (email: string) => {
    await invitations.send({
      email,
      role: "member",
      redirectUrl: "https://app.example.com/accept-invitation",
    });
  };

  return (
    <form onSubmit={(e) => {
      e.preventDefault();
      const email = new FormData(e.currentTarget).get("email") as string;
      handleInvite(email);
    }}>
      <input name="email" type="email" placeholder="colleague@company.com" />
      <button type="submit">Send Invitation</button>
    </form>
  );
}

Listing Pending Invitations

curl "https://api.hellojohn.dev/v1/organizations/org_01HABCDEF777666/invitations" \
  -H "Authorization: Bearer sk_live_abc123" \
  -H "X-Tenant-ID: tnt_01HABCDEF654321"

Response:

{
  "data": [
    {
      "id": "inv_01HABCDEF555999",
      "email": "bob@example.com",
      "role": "member",
      "status": "pending",
      "expires_at": "2024-01-22T10:00:00Z"
    }
  ]
}

Invitation statuses:

StatusDescription
pendingSent, not yet accepted
acceptedInvitee joined the organization
expiredPast the expiry date
revokedManually cancelled

Accepting an Invitation

Your app receives the invitation token via the redirect URL:

https://app.example.com/accept-invitation?token=inv_tok_abc123...

Call the accept endpoint with the token:

curl -X POST "https://api.hellojohn.dev/v1/invitations/inv_tok_abc123.../accept" \
  -H "X-Tenant-ID: tnt_01HABCDEF654321" \
  -H "Content-Type: application/json" \
  -d '{}'

If the invitee is not yet signed in, include their credentials:

curl -X POST "https://api.hellojohn.dev/v1/invitations/inv_tok_abc123.../accept" \
  -H "X-Tenant-ID: tnt_01HABCDEF654321" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Bob Smith",
    "password": "SecurePassword123!"
  }'

If the email matches an existing account, the user must sign in first. If not, a new account is created with the provided credentials.

Response: 200 OK

{
  "user_id": "usr_01HABCDEF789012",
  "organization_id": "org_01HABCDEF777666",
  "role": "member",
  "access_token": "eyJhbGciOiJFZERTQSJ9...",
  "refresh_token": "rt_01HABCDEF..."
}

Accept Flow with SDK

import { useHelloJohn } from "@hellojohn/react";

function AcceptInvitationPage() {
  const { invitations } = useHelloJohn();
  const token = new URLSearchParams(window.location.search).get("token");

  const handleAccept = async () => {
    const result = await invitations.accept(token!);
    // result.organization — the org the user joined
    window.location.href = "/dashboard";
  };

  return (
    <div>
      <h1>You've been invited!</h1>
      <button onClick={handleAccept}>Accept Invitation</button>
    </div>
  );
}

Revoking an Invitation

Cancel a pending invitation before it's accepted:

curl -X DELETE "https://api.hellojohn.dev/v1/organizations/org_01HABCDEF777666/invitations/inv_01HABCDEF555999" \
  -H "Authorization: Bearer sk_live_abc123" \
  -H "X-Tenant-ID: tnt_01HABCDEF654321"

Response: 204 No Content


Resending an Invitation

If the invitee didn't receive the email:

curl -X POST "https://api.hellojohn.dev/v1/organizations/org_01HABCDEF777666/invitations/inv_01HABCDEF555999/resend" \
  -H "Authorization: Bearer sk_live_abc123" \
  -H "X-Tenant-ID: tnt_01HABCDEF654321"

This sends a fresh email with a new token. The old token is invalidated.


Customizing the Invitation Email

See Email API — Templates to customize the invitation email template with your branding.


On this page