Keely for developers
Authorization.
OAuth 2.1 with PKCE, on the authorization server Keely already runs for assistants. Your app never sees a password or an email; it gets a token for one person, which they can take back at any time.
Updated
On this page
The API uses OAuth 2.1: the authorization-code flow with PKCE (S256), on the authorization server Keely already runs for assistants, Supabase Auth. Your app never sees the person’s password or email. It gets an access token for that person, for its own use, and the person can take it back at any time.
- Issuer
https://zndhrjuvjoxbcdmwzeim.supabase.co/auth/v1- Discovery
https://zndhrjuvjoxbcdmwzeim.supabase.co/.well-known/oauth-authorization-server/auth/v1- Authorize
GET https://zndhrjuvjoxbcdmwzeim.supabase.co/auth/v1/oauth/authorize- Token
POST https://zndhrjuvjoxbcdmwzeim.supabase.co/auth/v1/oauth/token- Resource metadata
https://meetkeely.com/.well-known/oauth-protected-resource/api/v1- Tokens
- Access tokens live one hour; refresh tokens are always issued, and rotated
Confidential and public clients
When you register, you say what kind of client your app is:
- A server that keeps a secret (confidential). You get a
client_idand aclient_secret. The secret is shown once, in the portal, and Keely never stores it. The server authenticates at the token endpoint with HTTP Basic (client_secret_basic) or withclient_secretin the body (client_secret_post). Keep it on the server; never ship it in an app. - An app on a phone or computer, no secret (public). You get a
client_idonly, and send it alone at the token endpoint. PKCE is what protects the exchange.
Step 1: send the person to authorize
Make a code_verifier (43 to 128 random characters), its code_challenge (the Base64url-encoded SHA-256 of the verifier) and a state, then open:
https://zndhrjuvjoxbcdmwzeim.supabase.co/auth/v1/oauth/authorize
?client_id=YOUR_CLIENT_ID
&redirect_uri=https://example.com/callback
&response_type=code
&code_challenge=CODE_CHALLENGE
&code_challenge_method=S256
&state=STATE
&scope=email
scope=email is the authorization server’s own scope and says nothing about Keely’s data. Keely’s permissions are chosen on the consent page, from what your app registered: the person signs in with their Keely account, sees your app’s name and website, and ticks what it may read. Then they come back to your redirect_uri with code and state. The redirect_uri must be one you registered, exactly.
Step 2: exchange the code
curl -X POST https://zndhrjuvjoxbcdmwzeim.supabase.co/auth/v1/oauth/token \
-u "YOUR_CLIENT_ID:YOUR_CLIENT_SECRET" \
-d grant_type=authorization_code \
-d code=CODE \
-d redirect_uri=https://example.com/callback \
-d code_verifier=CODE_VERIFIER
A public client leaves out -u and sends -d client_id=YOUR_CLIENT_ID instead. The answer:
{
"access_token": "…",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "…"
}
Step 3: call the API
curl https://meetkeely.com/api/v1/me \
-H "Authorization: Bearer ACCESS_TOKEN"
Step 4: refresh
Access tokens live an hour. A refresh token is always issued, and every refresh returns a new one and retires the old, so store the new one each time.
curl -X POST https://zndhrjuvjoxbcdmwzeim.supabase.co/auth/v1/oauth/token \
-u "YOUR_CLIENT_ID:YOUR_CLIENT_SECRET" \
-d grant_type=refresh_token \
-d refresh_token=REFRESH_TOKEN
What the person chose
The consent page shows only the scopes your app registered, and the person may untick any of them. So never assume you got what you asked for: read scopes from GET /v1/me after connecting, and handle 403 insufficient_scope on every call, because the person can narrow a permission later and the change holds at your next request. There’s no way to ask for a scope your app didn’t register.
Disconnecting
There is no revocation endpoint. The person disconnects your app in the Keely app (Settings › Privacy › Apps and assistants) or at meetkeely.com/assistants. That revokes the grant and ends your app’s sessions and refresh tokens at once; from then on the API answers 401 invalid_token. Treat that answer on a token that hasn’t expired as a disconnect, and delete what you hold for that person (rule 3).