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.com for production. Replace the host with api-stage.onsched.com when calling the staging environment.

Migrating from v1: Older integrations used identity.onsched.com (OpenID Connect). v3 APIs use v3.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 APIOAuth2 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 userDashboard 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 siteAPI 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 CaseHeaders RequiredHow 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 UsersAuthorization: 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 Routesx-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

  1. Generate a client ID/secret pair in the dashboard (POST /v3/clientId, see /api/routes/clientId.js).
  2. 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"
  1. Use the access_token as a Bearer token for one hour. Tokens embed company_id, grant_type=client_credentials, and optional scopes.
  2. 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

  1. Authenticate via the v3 dashboard to receive a Supabase JWT.
  2. Include two headers on every API request:
    • Authorization: Bearer <JWT>
    • x-api-key: <Company API key>
  3. 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

  1. Obtain your company's API key and public client ID from the v3 dashboard.
  2. Include both headers on every public API request:
    • x-api-key: <Company API key>
    • x-client-id: <Public client ID>
  3. 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:

EndpointPurpose
POST /v3/oauth/tokenMachine / 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/tokenDashboard 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-key belongs to a company the current dashboard user is not a member of.
  • Invalid Client ID: Returned when the x-client-id header is missing, invalid, or expired (for public routes).
  • Scope violations: Client-credentials tokens may include a space-delimited scope string; 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.