Authentication Guide
Understand the three supported authentication methods: OAuth2 client credentials for server-to-server integrations, Supabase JWT tokens for dashboard users, and API key + public client ID for public routes.
Environment tip: All examples use
v3.onsched.comfor production. Replace the host withapi-stage.onsched.comwhen calling the staging environment.
Migrating from v1: Older integrations used
identity.onsched.com(OpenID Connect). v3 APIs usev3.onsched.com(or staging). Server-to-server access should use OAuth2 client credentials on/v3/oauth/token—not the dashboard refresh-token endpoints below.
Which flow do I need?
| If you are building… | Use this |
|---|---|
| A backend or cron job that calls the API | OAuth2 client credentials (/v3/oauth/token). Only client_id, client_secret, and short-lived access tokens—no API key step, no refresh token. |
| Something that acts as a logged-in dashboard user | Dashboard JWT + x-api-key. Optional refresh path only if you are managing a Supabase-style session (see Token refresh). |
| A public booking widget or unauthenticated site | API key + public client ID on /v3/public/* only. |
Do not chain API key → refresh token → access token for machine integrations—that mixes different models. API keys are not part of the OAuth2 client-credentials flow.
When to Use
- Secure server-to-server integrations that call the API directly.
- Configure dashboard-powered apps that rely on Supabase-issued JWTs.
- Enable public-facing booking flows that don't require user authentication.
- Rotate credentials without interrupting production traffic.
Auth Models
| Use Case | Headers Required | How to Obtain |
|---|---|---|
| OAuth2 Clients (recommended for API consumers) | Authorization: Bearer <access_token> | Exchange client_id and client_secret via POST /v3/oauth/token using the client_credentials grant. |
| Dashboard Users | Authorization: Bearer <user JWT> + x-api-key: <company key> | Retrieve from the v3 dashboard. Tokens map to specific users and expire according to Supabase settings. |
| Public Routes | x-api-key: <company key> + x-client-id: <public client id> | Obtain both from the v3 dashboard. Used for /v3/public/* endpoints that don't require user authentication. |
After authentication succeeds, the request is scoped to the correct company and user context. For machine tokens, the company comes from the JWT; for dashboard JWTs and public routes, x-api-key must match that company.
Headers at a glance
Authorization: Bearer <token>— OAuth2 access tokens (one hour) or dashboard JWTs.x-api-key— Scopes the request to a company. Required for dashboard JWTs and public routes; optional for machine tokens because the company is embedded in the token.x-client-id— Public client ID for/v3/public/*routes. Safe to expose in client apps but useless without a valid API key.- Rotate client secrets and API keys regularly; treat API keys as confidential except when used alongside public client IDs for unauthenticated booking flows.
OAuth2 Client Credentials Flow
- Generate a client ID/secret pair in the dashboard (
POST /v3/clientId, see/api/routes/clientId.js). - Request an access token:
curl -X POST https://v3.onsched.com/v3/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-u "<client_id>:<client_secret>" \
-d "grant_type=client_credentials&scope=read write"- Use the
access_tokenas a Bearer token for one hour. Tokens embedcompany_id,grant_type=client_credentials, and optional scopes. - Rotate secrets periodically; revoke compromised credentials by deleting them via
DELETE /v3/clientId/:id.
Machine tokens do not require x-api-key or x-client-id headers—the access token already identifies the company.
Dashboard Token Flow
- Authenticate via the v3 dashboard to receive a Supabase JWT.
- Include two headers on every API request:
Authorization: Bearer <JWT>x-api-key: <Company API key>
- The API cross-checks that the JWT user belongs to the company linked to the API key.
Use this flow when you need user-level auditing (for example, when exposing the API directly from your dashboard session). For backend integrations, prefer OAuth2.
Public Routes Flow
- Obtain your company's API key and public client ID from the v3 dashboard.
- Include both headers on every public API request:
x-api-key: <Company API key>x-client-id: <Public client ID>
- These credentials authenticate the company but do not identify a specific user. All public routes are scoped to
/v3/public/*endpoints.
Use this flow for customer-facing booking widgets, public availability displays, and other scenarios where end users don't have dashboard accounts.
Token refresh
Two different endpoints—do not confuse them:
| Endpoint | Purpose |
|---|---|
POST /v3/oauth/token | Machine / client credentials only. Same as OAuth2 Client Credentials Flow. Returns an access token. No refresh token. When it expires (~1 hour), call again with the same client_id and client_secret. |
POST /v3/auth/token | Dashboard session only. Body: { "refresh_token": "..." }. Exchanges a Supabase refresh token for a new access token (and may return a rotated refresh token). Not used for OAuth2 client credentials. |
Details:
- OAuth2: Access tokens expire after about one hour. Request a new access token with the same client credentials; no refresh token is issued.
- Dashboard: Owners may call
POST /v3/auth/generateRefreshToken(authenticated with a valid Bearer token) to mint a long-lived refresh token for integrations that mirror a user session. Store refresh tokens in a secret manager or secure storage—not in a cache with aggressive eviction unless you have another way to recover them. - Front-ends: Do not expose client secrets or long-lived refresh tokens in the browser; proxy token exchange through your backend.
Error Handling
- 401 Unauthorized: Missing or invalid headers, malformed Basic auth, expired JWT, or invalid public client ID.
- User not associated: Returned when
x-api-keybelongs to a company the current dashboard user is not a member of. - Invalid Client ID: Returned when the
x-client-idheader is missing, invalid, or expired (for public routes). - Scope violations: Client-credentials tokens may include a space-delimited
scopestring; your integration should only rely on scopes you requested at token issuance.
Always store secrets in a secure vault and avoid embedding them in client-side code. Use short-lived OAuth2 tokens for any automation or integration work. For public routes, ensure your public client ID is properly configured in the dashboard and hasn't expired.
Updated 11 days ago
