Documentation

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
  1. Confidential and public clients
  2. Step 1: send the person to authorize
  3. Step 2: exchange the code
  4. Step 3: call the API
  5. Step 4: refresh
  6. What the person chose
  7. Disconnecting

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_id and a client_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 with client_secret in 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_id only, 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:

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

Request
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:

Response
{
  "access_token": "…",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "…"
}

Step 3: call the API

Request
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.

Request
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).