Skip to main content
The Invitations service allows you to invite users to join your account and tracks the invitation status.

Overview

import { SnackBaseClient } from "@snackbase/sdk";

const client = new SnackBaseClient({
  baseUrl: "https://api.example.com",
});

// Access the invitations service
const invitations = client.invitations;

List Invitations

Get all invitations for the current account:
const invitations = await client.invitations.list();
With pagination:
const invitations = await client.invitations.list({
  limit: 20,
  skip: 0
});

Create an Invitation

Invite a new user to join your account:
const invitation = await client.invitations.create({
  email: "[email protected]",
  role_id: "role-id"
});
The response includes:
{
  "id": "invitation-id",
  "email": "[email protected]",
  "role_id": "role-id",
  "status": "pending",
  "token": "unique-invitation-token",
  "expires_at": "2024-02-15T00:00:00Z",
  "created_at": "2024-01-15T00:00:00Z"
}

Resend an Invitation

Resend the invitation email if it wasn’t received:
await client.invitations.resend("invitation-id");

Get Public Invitation Details

Retrieve invitation details using the token (no authentication required):
const invitation = await client.invitations.getPublic("invitation-token");
Useful for invitation acceptance pages where the user isn’t authenticated yet.

Accept an Invitation

Accept an invitation and create a user account:
const authResponse = await client.invitations.accept(
  "invitation-token",
  "new-password"
);
The response includes authentication tokens and the created user:
{
  "user": { ... },
  "account": { ... },
  "token": "access-token",
  "refresh_token": "refresh-token",
  "expires_at": "2024-01-16T00:00:00Z"
}

// The auth state is automatically stored
console.log(client.isAuthenticated); // true
console.log(client.user); // User object

Cancel an Invitation

Cancel a pending invitation:
await client.invitations.cancel("invitation-id");

Complete Example

Invitation management component:
import { useState } from "react";
import { useQuery, useMutation, useQueryClient } from "@snackbase/sdk/react";

function InvitationManager() {
  const queryClient = useQueryClient();
  const [email, setEmail] = useState("");

  const { data: invitations, isLoading } = useQuery({
    queryKey: ["invitations"],
    queryFn: () => client.invitations.list()
  });

  const createMutation = useMutation({
    mutationFn: (data) => client.invitations.create(data),
    onSuccess: () => {
      setEmail("");
      queryClient.invalidateQueries(["invitations"]);
    }
  });

  const resendMutation = useMutation({
    mutationFn: (id) => client.invitations.resend(id)
  });

  const cancelMutation = useMutation({
    mutationFn: (id) => client.invitations.cancel(id),
    onSuccess: () => {
      queryClient.invalidateQueries(["invitations"]);
    }
  });

  const handleCreate = (e) => {
    e.preventDefault();
    createMutation.mutate({ email, role_id: "default-role" });
  };

  if (isLoading) return <div>Loading...</div>;

  return (
    <div>
      <h2>User Invitations</h2>

      <form onSubmit={handleCreate}>
        <input
          type="email"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
          placeholder="[email protected]"
          required
        />
        <button type="submit" disabled={createMutation.isPending}>
          Send Invitation
        </button>
      </form>

      <table>
        <thead>
          <tr>
            <th>Email</th>
            <th>Status</th>
            <th>Expires</th>
            <th>Actions</th>
          </tr>
        </thead>
        <tbody>
          {invitations?.map(inv => (
            <tr key={inv.id}>
              <td>{inv.email}</td>
              <td>{inv.status}</td>
              <td>{new Date(inv.expires_at).toLocaleDateString()}</td>
              <td>
                {inv.status === "pending" && (
                  <>
                    <button
                      onClick={() => resendMutation.mutate(inv.id)}
                      disabled={resendMutation.isPending}
                    >
                      Resend
                    </button>
                    <button
                      onClick={() => cancelMutation.mutate(inv.id)}
                      disabled={cancelMutation.isPending}
                    >
                      Cancel
                    </button>
                  </>
                )}
              </td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

Invitation Acceptance Flow

For public invitation acceptance pages:
import { useEffect, useState } from "react";
import { SnackBaseClient } from "@snackbase/sdk";

const client = new SnackBaseClient({
  baseUrl: "https://api.example.com"
});

function InvitationAcceptPage({ token }) {
  const [invitation, setInvitation] = useState(null);
  const [password, setPassword] = useState("");
  const [error, setError] = useState(null);

  useEffect(() => {
    client.invitations.getPublic(token)
      .then(setInvitation)
      .catch(setError);
  }, [token]);

  const handleAccept = async (e) => {
    e.preventDefault();
    try {
      await client.invitations.accept(token, password);
      // User is now logged in, redirect to dashboard
      window.location.href = "/dashboard";
    } catch (err) {
      setError(err.message);
    }
  };

  if (!invitation) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;

  return (
    <div>
      <h1>Accept Invitation</h1>
      <p>You've been invited to join {invitation.account_name}</p>

      <form onSubmit={handleAccept}>
        <input
          type="password"
          value={password}
          onChange={(e) => setPassword(e.target.value)}
          placeholder="Create a password"
          required
          minLength={8}
        />
        <button type="submit">Accept & Create Account</button>
      </form>
    </div>
  );
}

Next Steps