API Authentication
Learn how to authenticate with the PlanOps API using Clerk-issued JWT tokens.
Overview
PlanOps uses Clerk for authentication. Every API request must include a valid JWT (JSON Web Token) issued by Clerk in the Authorization header.
GET /api/v1/projects
Host: test-api.projectjump.app
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
Authentication Flow
sequenceDiagram
participant User
participant Frontend
participant Clerk
participant API
User->>Frontend: Sign in
Frontend->>Clerk: Authenticate user
Clerk->>Frontend: JWT token
Frontend->>API: API request with Bearer token
API->>Clerk: Verify token (JWKS)
Clerk->>API: Token valid
API->>Frontend: Response
For Frontend Developers
If you're building a web or mobile app, use Clerk's SDK to handle authentication:
Web (React/Next.js)
import { useAuth } from '@clerk/clerk-react';
function useApiClient() {
const { getToken } = useAuth();
return async function callAPI(endpoint: string, options: RequestInit = {}) {
// Get fresh token from Clerk
const token = await getToken();
const headers = new Headers(options.headers || {});
if (token) {
headers.set('Authorization', `Bearer ${token}`);
}
return fetch(`https://test-api.projectjump.app${endpoint}`, {
...options,
headers,
});
};
}
// Usage
const api = useApiClient();
const response = await api('/api/v1/projects');
const projects = await response.json();
Mobile (React Native)
import { useAuth } from '@clerk/clerk-expo';
function ProjectsList() {
const { getToken } = useAuth();
async function fetchProjects() {
const token = await getToken();
const response = await fetch('https://test-api.projectjump.app/api/v1/projects', {
headers: {
'Authorization': `Bearer ${token}`,
},
});
return response.json();
}
// ... rest of component
}
For API/Script Developers
If you're building integrations, backend services, or testing the API directly, you'll need to obtain a Clerk token programmatically.
Option 1: Get Token from Frontend (Development/Testing)
For development and testing, you can extract a token from your logged-in session:
- Sign in to PlanOps in your browser
- Open browser DevTools (F12)
- Run this in the console:
// If using Clerk React SDK
const { getToken } = window.Clerk;
const token = await getToken();
console.log(token);
- Copy the token and use it in your API calls
Example with cURL:
curl https://test-api.projectjump.app/api/v1/projects \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
Example with Python:
import requests
token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..." # Token from browser
response = requests.get(
'https://test-api.projectjump.app/api/v1/projects',
headers={'Authorization': f'Bearer {token}'}
)
projects = response.json()
print(projects)
Tokens obtained this way expire after 60 minutes by default. You'll need to get a fresh token when it expires.
Option 2: OAuth2/OIDC Flow (Production Integrations)
For production integrations, use Clerk's OAuth2/OIDC flow:
Authorization Code Flow
- Redirect user to Clerk:
https://your-clerk-domain.clerk.accounts.dev/oauth/authorize?
client_id=YOUR_CLIENT_ID&
redirect_uri=YOUR_REDIRECT_URI&
response_type=code&
scope=openid email profile
-
User signs in and authorizes your app
-
Clerk redirects back with an authorization code:
https://your-app.com/callback?code=AUTH_CODE
- Exchange code for tokens:
import requests
response = requests.post(
'https://your-clerk-domain.clerk.accounts.dev/oauth/token',
data={
'grant_type': 'authorization_code',
'code': 'AUTH_CODE',
'client_id': 'YOUR_CLIENT_ID',
'client_secret': 'YOUR_CLIENT_SECRET',
'redirect_uri': 'YOUR_REDIRECT_URI',
}
)
tokens = response.json()
access_token = tokens['access_token']
- Use the access token:
api_response = requests.get(
'https://test-api.projectjump.app/api/v1/projects',
headers={'Authorization': f'Bearer {access_token}'}
)
Get OAuth2 Credentials
To use OAuth2:
- Contact PlanOps support to register your application
- Provide your redirect URI(s)
- Receive your
client_idandclient_secret
Option 3: Service Account Tokens (Internal/Admin)
For internal services or admin operations, PlanOps provides a token generation script:
# From the backend repository
python scripts/get_test_token.py
This generates a fresh JWT token for the configured service account and copies it to your clipboard.
Configuration required in .env:
CLERK_SECRET_KEY=sk_test_...
CLERK_SERVICE_USER_ID=user_... # or CLERK_SERVICE_USER_EMAIL
CLERK_ISSUER=https://your-clerk-domain.clerk.accounts.dev
CLERK_JWKS_URL=https://your-clerk-domain.clerk.accounts.dev/.well-known/jwks.json
Using Your Access Token
Include the token in the Authorization header with the Bearer scheme:
cURL
curl https://test-api.projectjump.app/api/v1/projects \
-H "Authorization: Bearer YOUR_TOKEN"
Python
import requests
headers = {
'Authorization': 'Bearer YOUR_TOKEN',
'Content-Type': 'application/json',
}
# GET request
response = requests.get(
'https://test-api.projectjump.app/api/v1/projects',
headers=headers
)
# POST request
response = requests.post(
'https://test-api.projectjump.app/api/v1/projects',
headers=headers,
json={'name': 'New Project', 'description': 'Project description'}
)
JavaScript/TypeScript
const token = 'YOUR_TOKEN';
// GET request
const response = await fetch('https://test-api.projectjump.app/api/v1/projects', {
headers: {
'Authorization': `Bearer ${token}`,
},
});
// POST request
const response = await fetch('https://test-api.projectjump.app/api/v1/projects', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: 'New Project',
description: 'Project description',
}),
});
Token Expiration and Refresh
JWT tokens expire after a period (default: 60 minutes). When your token expires:
Frontend Apps (Automatic)
Clerk SDKs automatically refresh tokens. Just call getToken() each time:
// Always get a fresh token - Clerk handles caching/refresh
const token = await getToken();
Backend Services (Manual)
Re-run the token generation process:
- OAuth2: Use the refresh token (if requested) or re-authenticate
- Service Account: Re-run
scripts/get_test_token.py - Browser: Get a new token from DevTools console
Error Responses
401 Unauthorized
Your token is missing, invalid, or expired:
{
"detail": "Invalid token: Signature has expired"
}
Solution: Get a fresh token and retry.
403 Forbidden
Your token is valid but you lack permission:
{
"detail": "Insufficient permissions to access this resource"
}
Solution: Ensure your user has the required role/permissions.
Security Best Practices
- Never commit tokens to version control
- Use environment variables for tokens in scripts/apps
- Rotate tokens regularly for long-running services
- Use HTTPS only for API requests
- Validate tokens server-side - never trust client-only validation
Next Steps
- API Reference - Explore available endpoints
- Getting Started Guide - Build your first integration
- Common Workflows - Learn common API patterns
Need Help?
- Authentication issues? Check Clerk's documentation
- API errors? Review the API Reference for endpoint details
- Integration support? Contact PlanOps support through your dashboard
Last updated: 2025-12-12