Skip to main content

OAuth 2.0

OAuth 2.0 is the standard way for third-party applications and AI agents — such as Claude, custom integrations, or partner tools — to act on behalf of SchedPilot users without ever handling their password or personal API key.

When to Use OAuth vs. API Keys

ScenarioUse
Personal script or automation that only you will runAPI Key
App, integration, or AI agent that other SchedPilot users will authorizeOAuth 2.0
CI/CD pipeline or server-side job you controlAPI Key
Marketplace app or plugin built for public useOAuth 2.0

If you are building something that other people will connect to their SchedPilot accounts, OAuth is the right choice. It gives users full visibility and control — they can see what your app has access to and revoke it at any time.

Flow Overview

SchedPilot implements the Authorization Code flow. The steps are:

  1. Register your app in the SchedPilot dashboard to get a client_id and client_secret.
  2. Redirect the user to the SchedPilot consent page.
  3. Exchange the authorization code for an access token.
  4. Call the API using the access token.

Step 1: Register an OAuth App

  1. Log in to app.schedpilot.com and navigate to API Access.
  2. Scroll to the OAuth Apps section and click Register New App.
  3. Fill in:
    • App name — displayed to users on the consent screen.
    • Redirect URI — the URL SchedPilot will send the user back to after they approve or deny access. Must be an exact match (scheme, host, path, and query string).
  4. Click Register.

You will receive:

ValuePrefixNotes
client_idsp_cid_Safe to include in client-side code.
client_secretsp_cs_Shown once only. Store it immediately in a secret manager. Never expose it in client-side code or version control.

Step 2: Redirect the User to Authorize

Send the user to the SchedPilot consent page:

GET https://app.schedpilot.com/oauth/authorize

Query parameters

ParameterRequiredDescription
client_idYesYour app's sp_cid_... value.
redirect_uriYesMust exactly match the URI registered in Step 1.
response_typeYesMust be code.
stateRecommendedA random, unguessable string. SchedPilot will echo it back to you after the redirect. Use it to prevent CSRF attacks.

Example authorization URL

https://app.schedpilot.com/oauth/authorize
?client_id=sp_cid_abc123
&redirect_uri=https%3A%2F%2Fmyapp.example.com%2Fauth%2Fcallback
&response_type=code
&state=xK9p2mNqR7sT4uWv

The user will see a consent screen showing your app name and will be asked to approve or deny access.

  • Approved: SchedPilot redirects to your redirect_uri with ?code=AUTH_CODE&state=YOUR_STATE.
  • Denied: SchedPilot redirects to your redirect_uri with ?error=access_denied&state=YOUR_STATE.

Step 3: Exchange the Code for an Access Token

After the user approves access, exchange the short-lived code for a long-lived access token. This request must be made server-side so your client_secret is never exposed.

Endpoint

POST https://api.schedpilot.com/developers/v1/oauth/token

Request body (JSON)

FieldTypeRequiredDescription
grant_typestringYesMust be authorization_code.
client_idstringYesYour sp_cid_... value.
client_secretstringYesYour sp_cs_... value.
codestringYesThe code received in the redirect query string.

Example request

curl -X POST "https://api.schedpilot.com/developers/v1/oauth/token" \
-H "Content-Type: application/json" \
-d '{
"grant_type": "authorization_code",
"client_id": "sp_cid_abc123",
"client_secret": "sp_cs_your_secret_here",
"code": "AUTH_CODE_FROM_REDIRECT"
}'

Response

{
"access_token": "sp_tok_xxxxxxxxxxxxxxxxxxxxxxxx",
"token_type": "bearer"
}

Store the access_token securely — treat it with the same care as a password.


Step 4: Use the Access Token

Pass the token in the Authorization header on every SchedPilot API request:

curl "https://api.schedpilot.com/developers/v1/accounts" \
-H "Authorization: Bearer sp_tok_xxxxxxxxxxxxxxxxxxxxxxxx"

The token grants access to all /developers/v1 endpoints on behalf of the user who authorized your app.


Token Lifecycle

  • OAuth access tokens do not expire by default. Once issued, a token remains valid until explicitly revoked.
  • Users can review all active tokens and revoke them at any time from app.schedpilot.com > API Access > Active OAuth Tokens.
  • If your app detects a 401 Unauthorized or 403 Forbidden response with an sp_tok_ token, the user has likely revoked it. Restart the OAuth flow to re-authorize.

Complete Node.js + Express Example

The following example shows the full OAuth flow — redirecting the user, handling the callback, and exchanging the code for a token.

const express = require('express');
const crypto = require('crypto');
const fetch = require('node-fetch'); // or use the built-in fetch in Node 18+

const app = express();

const CLIENT_ID = process.env.SCHEDPILOT_CLIENT_ID;
const CLIENT_SECRET = process.env.SCHEDPILOT_CLIENT_SECRET;
const REDIRECT_URI = 'https://myapp.example.com/auth/callback';

// In-memory store for state tokens (use a real store in production)
const pendingStates = new Set();

// Step 2: Redirect the user to the SchedPilot consent page
app.get('/auth/schedpilot', (req, res) => {
const state = crypto.randomBytes(16).toString('hex');
pendingStates.add(state);

const params = new URLSearchParams({
client_id: CLIENT_ID,
redirect_uri: REDIRECT_URI,
response_type: 'code',
state,
});

res.redirect(`https://app.schedpilot.com/oauth/authorize?${params}`);
});

// Step 3: Handle the redirect and exchange the code for a token
app.get('/auth/callback', async (req, res) => {
const { code, state, error } = req.query;

// User denied access
if (error === 'access_denied') {
return res.status(403).send('Access denied by user.');
}

// Validate the state parameter to prevent CSRF
if (!state || !pendingStates.has(state)) {
return res.status(400).send('Invalid or missing state parameter.');
}
pendingStates.delete(state);

// Exchange the code for an access token
const response = await fetch('https://api.schedpilot.com/developers/v1/oauth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
grant_type: 'authorization_code',
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
code,
}),
});

if (!response.ok) {
return res.status(500).send('Token exchange failed.');
}

const { access_token } = await response.json();

// Store access_token securely (database, encrypted session, etc.)
// req.session.schedpilotToken = access_token;

res.send('Authorization successful! You can now use SchedPilot on behalf of this user.');
});

// Step 4: Make an API call using the token
app.get('/accounts', async (req, res) => {
// Retrieve the token from wherever you stored it
const token = req.session?.schedpilotToken;

const response = await fetch('https://api.schedpilot.com/developers/v1/accounts', {
headers: { 'Authorization': `Bearer ${token}` },
});

const accounts = await response.json();
res.json(accounts);
});

app.listen(3000, () => console.log('Listening on port 3000'));

Managing OAuth Apps & Tokens

OAuth apps and active tokens are managed entirely through the SchedPilot dashboard — no API calls needed.

Go to app.schedpilot.com/api-access and scroll to the OAuth Apps section to:

  • Register a new app (get your client_id and one-time client_secret)
  • View active token count per app
  • Delete an app (revokes all its tokens immediately)
  • View and revoke individual active tokens under Active OAuth Tokens

Security Best Practices

PracticeWhy
Store client_secret server-side onlyExposing it in client-side code allows anyone to impersonate your app.
Validate the state parameter on every callbackPrevents CSRF attacks that could trick users into authorizing malicious requests.
Use https:// for your redirect_uriPrevents the authorization code from being intercepted in transit.
Store access_token in encrypted storageTreat it like a password — it grants full API access on the user's behalf.
Handle 401/403 gracefullyRe-run the OAuth flow if the user has revoked your token.