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 |
scope |
optional — omit for a data connection; organization for the back-office sign-in |
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.) scope is what
the user actually consented to — read it rather than assuming what you asked
for; it is how an organization-access
ceremony tells you whether the person turned out to be a member.
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.
6. Organization access (back-office sign-in)
The ceremony has a second use: letting a member of your own organization sign in to your back office and act under that membership — reading the copies your organization kept, deciding update proposals, attaching files — through the Organization API. It is the same popup, the same code exchange and the same token lifecycle, with three differences:
scope=organization(alongside the identity scopes:scope=profile offline_access organization) and nomanifest_idorrequest_id. Organization access is a pure login — it never rides on a data ask, and combining the two is refused withinvalid_requestbefore any screen renders. The app needs Organization access switched on (Organization → Apps in the dashboard; for a client the Geena team registered, ask them). Asking for a scope outside the app’s ceiling is refused, never silently narrowed.- The scope means “if member”. A person who holds an active membership of
the organization that owns your client sees an Organization access consent
— “App wants to act under your membership of Organization”, the three
things the app may then do as them (read the organization’s requests and
copies, keep and decide on its behalf, never touch their own vault), and a
revocation note — and the button reads Accept and sign in. Anyone else
gets the plain login binding instead: no error, no visible difference on the
screen. The token response’s
scopeis how your backend learns which it got —organizationpresent means the token opens/org/v1; absent means this person is not a member, so route them to your ordinary user flow. - Membership is re-checked on every call, and so is the member’s role:
reading needs
request_read, keeping and deciding needrequest_manage, attaching files needs the file permissions — exactly what the dashboard requires of the same person. Removing the member from your organization ends their access on the next call; the token itself does not need to be revoked.
A member whose consent already covers the scope skips the screen on return, so “sign in to the back office” is one round-trip for everyone but first-timers.