# Authorization.

> https://meetkeely.com/developers/authorization · Updated 2026-11-06 · Keely, by Metrica Fit LLC

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:

```text 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

```bash 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:

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

## Step 3: call the API

```bash 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.

```bash 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`](https://meetkeely.com/developers/api/me#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](https://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](https://meetkeely.com/developers/rules#rules)).
