Invitations
Invite someone to your app by email. Hypery sends one email in your brand. Its button opens Hypery's sign-in page, where the invited address continues with no second email, or the person picks any other account.
An invitation is how your app brings a person in by email: "Sam invited you to edit Launch plan", "Join the Acme team", "Your client shared a proof with you".
You call one endpoint from your server, and Hypery emails the person in your app's name and brand. The button opens your page, and your page starts your normal OAuth sign-in. Hypery's sign-in page then shows every way to sign in, plus one more at the top of the email section:
- Continue as invited address. Signs that address in straight away, creating the account if there isn't one. There is no "check your inbox" step, because opening the invitation already proved the person reads that inbox.
- Any other method: Google, GitHub, Apple, a password, a passkey, or a different email address. This is the usual sign-in, so the person can accept your invitation with whichever account they want.
Either way the sign-in completes back to your app, and you attach the invitation to whichever account comes back.
Why Hypery sends the email
Opening the invitation is what lets the invited address skip the sign-in email, so the link must only ever reach that inbox. If your app could create such a link itself, anyone holding your client secret could sign in as any Hypery user. Instead your app supplies the words and the landing page, and Hypery delivers the link to the address.
At a glance
Send an invitation
POST https://hypery.ai/api/oauth/invitations
Authorization: Basic base64(client_id:client_secret)
Content-Type: application/json
{
"email": "sam@example.com",
"return_to": "https://yourapp.com/invite/9f2c?signin=1",
"title": "Alex invited you to edit “Launch plan”",
"message": "Alex shared a file with you on YourApp.",
"action_label": "Open in YourApp"
}curl https://hypery.ai/api/oauth/invitations \
-u "$HYPERY_CLIENT_ID:$HYPERY_CLIENT_SECRET" \
-H "content-type: application/json" \
-d '{
"email": "sam@example.com",
"return_to": "https://yourapp.com/invite/9f2c?signin=1",
"title": "Alex invited you to edit “Launch plan”",
"action_label": "Open in YourApp"
}'// On your server only: the client secret must never reach a browser.
export async function sendInvitation(invite: {
email: string; returnTo: string; title: string; message?: string; actionLabel?: string;
}): Promise<boolean> {
const auth = Buffer.from(`${process.env.HYPERY_CLIENT_ID}:${process.env.HYPERY_CLIENT_SECRET}`).toString("base64");
const res = await fetch("https://hypery.ai/api/oauth/invitations", {
method: "POST",
headers: { "content-type": "application/json", authorization: `Basic ${auth}` },
body: JSON.stringify({
email: invite.email,
return_to: invite.returnTo,
title: invite.title,
message: invite.message,
action_label: invite.actionLabel,
}),
});
if (!res.ok) console.error("invitation failed", res.status, await res.text());
return res.ok;
}This is a server-to-server call. It needs your app's client_secret, which
must never reach a browser. You can also send client_id and client_secret in
the JSON body instead of HTTP Basic.
All text is plain: Hypery escapes it, so HTML is shown as written. The email uses your app's name, logo and brand colour from its app settings, is sent as "Your App via Hypery". It tells the person they can continue with the invited address or use any other account.
Response
{ "sent": true, "expires_at": "2026-10-09T17:00:00.000Z" }The page at return_to
The person arrives signed out of your app. Start your usual sign-in. With
@hyperyai/sdk, that's login(), as a full-page redirect; a popup needs a click:
"use client";
import { useEffect } from "react";
import { useAuth } from "@hyperyai/sdk";
export function InviteLanding() {
const { user, isLoading, login } = useAuth();
useEffect(() => {
if (isLoading) return;
if (!user) {
// Remember this page so your OAuth callback can come back to it.
localStorage.setItem("return-to", window.location.pathname);
void login();
}
// Signed in: accept the invitation for this account, then show what it was for.
}, [isLoading, user, login]);
return null;
}- Accept the invitation for whichever account signs in. Put your own reference
in
return_to(an invite id, or a signed token) so the page knows which invitation it is accepting. Don't require the account's email to match the invited address: the person may choose another account. - New people see Hypery's one-time "Allow Your App" consent screen after signing in. People who have used your app before go straight through.
- Show a normal sign-in button too, for someone who cancels or comes back later.
How "Continue as" works
- The email's button opens a Hypery link, not your page directly. Hypery stores
the invitation in an http-only cookie in this browser, for one hour, then
forwards to
return_to. Nobody is signed in by this step. - Your page starts OAuth, and Hypery's sign-in page asks whether this browser has an unused invitation. If it does, the email section shows Continue as address, with every other method still below it.
- Choosing Continue as uses the invitation up and signs that address in, creating a verified account if there isn't one. Sign-in then continues to your app exactly like any email sign-in.
Opening the invitation on another device or in another browser, or more than an hour later, simply shows the normal sign-in page. The person can still use any method, including the usual emailed sign-in link.
If the account has two-factor authentication, Hypery still asks for the code after Continue as. The invitation proves the inbox, not the second factor.
Security
- Only Hypery can mint the shortcut. Your app never sees a link that signs
anyone in, so a leaked
client_secretcan send invitations (rate-limited) but cannot take over accounts. return_tois pinned to your origins. Hypery refuses anyreturn_tothat isn't on an origin you registered a redirect URI on, so invitations can't be used to send people elsewhere.- Plain text only. Your
title,messageandaction_labelare escaped. They can't add links or markup to the email. - Your users stay in control. Nothing happens on click; the person always chooses how to sign in.
Testing locally
- Register
http://localhost:<port>/callback(or your dev callback) as a redirect URI, and use areturn_toon that same origin. - Without an email provider configured, a local Hypery prints the email, including its link, to the server console instead of sending it.
- Open the link in the same browser you'll sign in with, so Continue as appears.
Common questions
The person already has an account. Send the invitation the same way. Continue as signs them in to their existing account, and any other method works too.
They signed in with a different account than the one I invited. That's allowed by design: accept the invitation for the account that came back. If your app needs the exact invited address (for example, regulated access), check the signed-in email on your side and explain what to do if it differs.
Can I resend? Yes. Each call sends a new email with its own 7-day invitation, within the limits. Older invitations keep working until they expire or are used.
Can I withdraw an invitation? Not through Hypery. Keep the state on your side,
the reference in return_to, and refuse it on your page once withdrawn. The email
still opens your page, which then says so.
Expiry and reuse
- An invitation lasts 7 days. Continue as works once.
- Clicking the email again still lands on
return_to, and the person signs in the usual way. - After 7 days, the button shows an "invitation expired" page. Send a new invitation.
Limits
- Per app: 60 invitations a minute, 600 an hour, 5,000 a day.
- Per recipient address, across all apps: 5 a minute, 20 an hour, 50 a day.
Over either limit, the call returns 429. Don't retry in a loop; wait and try
later.
Related
- OAuth: the sign-in your
return_topage starts. - Apps: the name, logo, colour and redirect URIs the
email and
return_tocheck use. - Connected apps: what people see on their side.
- Scopes