BloopAI/vibe-kanban · error · Error

Failed to accept invitation (${res.status})

Error message

Failed to accept invitation (${res.status})

What it means

acceptInvitation POSTs to {API_BASE}/v1/invitations/{token}/accept with a Bearer access token to join the invited organization. Any non-ok response triggers this generic Error containing only the status code. Unlike refreshTokens, it does not attach the status as a property, so callers can only parse the message to distinguish causes.

Source

Thrown at packages/remote-web/src/shared/lib/api.ts:137

  if (!res.ok) {
    throw new Error(`Invitation not found (${res.status})`);
  }
  return res.json();
}

export async function acceptInvitation(
  token: string,
  accessToken: string,
): Promise<AcceptInvitationResponse> {
  const res = await fetch(`${API_BASE}/v1/invitations/${token}/accept`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${accessToken}`,
    },
  });
  if (!res.ok) {
    throw new Error(`Failed to accept invitation (${res.status})`);
  }
  return res.json();
}

export async function refreshTokens(
  refreshToken: string,
): Promise<{ access_token: string; refresh_token: string }> {
  const res = await fetch(`${API_BASE}/v1/tokens/refresh`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ refresh_token: refreshToken }),
  });
  if (!res.ok) {
    const err = new Error(`Token refresh failed (${res.status})`);
    (err as Error & { status: number }).status = res.status;
    throw err;
  }
  return res.json();

View on GitHub (pinned to 4deb7eca8f)

Solutions

  1. Ensure the user is logged in as the invited account and pass a fresh access token; call refreshTokens if it may be stale.
  2. If status is 401/403, re-authenticate (the token is expired or for the wrong user).
  3. If status is 404/410, request a new invitation from the admin.
  4. Check the server response body (e.g. via devtools) for the specific rejection reason and surface it to the user.

Example fix

// before
if (!res.ok) {
  throw new Error(`Failed to accept invitation (${res.status})`);
}
// after
if (!res.ok) {
  const err = new Error(`Failed to accept invitation (${res.status})`);
  (err as Error & { status: number }).status = res.status;
  throw err;
}
Defensive patterns

Strategy: try-catch

Validate before calling

const accessToken = await getToken();
if (!accessToken) {
  // force login before attempting acceptance
  redirectToLogin();
}

Type guard

function isAcceptInvitationResponse(v: unknown): v is AcceptInvitationResponse {
  const r = v as AcceptInvitationResponse;
  return !!r && typeof r.organization_id === 'string' && typeof r.role === 'string';
}

Try / catch

try {
  const result = await acceptInvitation(token, accessToken);
  navigate(`/org/${result.organization_slug}`);
} catch (e) {
  const status = Number(/\((\d{3})\)$/.exec((e as Error).message)?.[1]);
  if (status === 401 || status === 403) {
    await triggerRefresh().catch(() => redirectToLogin());
  } else {
    show('Unable to accept this invitation — it may be expired or already used.');
  }
}

Prevention

When it happens

Trigger: POST /v1/invitations/{token}/accept returns 401/403 (access token expired, invalid, or belonging to a different user than the invitee), 404 (token unknown/already accepted), 410 (expired invitation), or 409/422 (user already a member, role conflict).

Common situations: User's session token expired between login and accepting; the access token passed in is from a different account than the invited email; invitation link reused after acceptance; invite expired while the user sat on the acceptance page.

Related errors


AI-assisted analysis of BloopAI/vibe-kanban@4deb7eca8f (2026-08-29). Data as JSON: /api/errors/52d90c377bfee2b2. Report an issue: GitHub.