Connect with Geena
Connect with Geena
One button in your app opens a popup on Geena’s origin; the user logs in
(email + one-time code — first-time users are provisioned on the spot),
sees one consent screen with your manifest’s asks and terms, and the popup
closes. Your backend then exchanges a short-lived code for tokens and the
connection’s request_id. Returning users with a live consent skip the
screen entirely — the popup flashes and closes.
The flow is standard OAuth 2.0 authorization-code with PKCE (S256, mandatory),
plus one Geena-specific parameter: manifest_id, which tells the ceremony
what you are asking for.
Two ways a connection starts
Every manifest carries an initiation policy — organization-initiated, recipient-initiated, or both — chosen when it is authored:
- Recipient-initiated (this page). Your button carries
manifest_id; the user’s click is the request. This is the lane for self-service signup and most product flows, and it needs a manifest whose policy allows recipient initiation. An organization-only manifest refuses the ceremony before any screen renders — except for a user who already holds an active connection, because a returning user’s ceremony is a login, which the policy never gates. - Organization-initiated. Your team creates the request against a chosen
person in the Geena dashboard (Manifests → send), and Geena invites them by
email; they accept on Geena’s own pages, no integration involved. Your app
meets the connection afterwards: run a pure-login ceremony
and list
GET /partner/v1/requeststo discover it — or, if your systems recorded the request id at send time, passrequest_idin the authorize URL to resume that exact invitation in-app.
Both lanes end in the same place: an active connection addressed by
request_id, indistinguishable on every other route.
1. The authorize URL
Open a popup (synchronously, inside the click handler — popup blockers) to:
| Parameter | Value |
|---|---|
response_type |
code |
client_id |
your registered client id |
redirect_uri |
a page on one of your registered origins (exact origin match) |
state |
random value you verify on return (CSRF) |
code_challenge |
BASE64URL(SHA-256(verifier)) — PKCE S256, required |
code_challenge_method |
S256 |
manifest_id |
the manifest this button asks consent for |
manifest_id is deployment configuration of your app, next to your
client_id: publish the manifest in the Geena dashboard and copy its id.
Connections are one per (user, organization, manifest) — the same button
on the same user resumes the existing connection instead of duplicating it, so
the call is safely repeatable. A different product flow with a different ask
is simply a different manifest_id on a different button.
Info
Omitting manifest_id runs a pure login ceremony: the user consents to the
identity connection only and no data connection is created. You can also pass
request_id instead, to resume a specific invitation your organization sent
by email — useful only if you carry Geena request ids in your own records;
most integrations should let manifest_id do the work.
2. Popup + PKCE snippet
On mobile, fall back to a full-page redirect to the same URL — the flow is identical, only the window management differs.
3. The landing page on your origin
The browser returns to your redirect_uri with ?code=...&state=... (or
?error=access_denied if the user declined). Serve a tiny page there that
hands the result to the opener and closes:
4. Exchange the code on your backend
Send the code and PKCE verifier to your own backend; the exchange requires
your client_secret and must never happen in the browser. The code is
single-use and expires after 5 minutes; redirect_uri must repeat the
value used at authorize.
request_id is the connection this ceremony minted or resumed — store it with
your user record; every /partner/v1 route is addressed by it. (It is absent
on pure-login ceremonies; you can always re-discover connections via
GET /partner/v1/requests.)
5. Token lifecycle
- Access tokens live 15 minutes. Refresh silently with the
refresh_tokengrant; the partner session rolls forward on each refresh, so an actively used integration does not expire. - Refresh tokens rotate. Every refresh response carries a new
refresh_token; always persist the latest. Presenting a superseded or revoked refresh token is treated as theft evidence: the whole token family and its session are revoked, and you must reconnect through the ceremony.
interaction_requiredon any token call means the user’s consent was revoked (they can do this from their Geena dashboard at any time) — open the ceremony popup again; nothing else will mint tokens.- Disconnect cleanly with RFC 7009 revocation — present any of your refresh tokens and the whole grant is torn down (consent, sessions, token families):
Tip
The token endpoints are per-IP rate limited. Exchange and refresh from your
backend, cache access tokens for their full 15 minutes, and never poll
/oauth/token.