Skip to main content

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:

  1. Sign in to PlanOps in your browser
  2. Open browser DevTools (F12)
  3. Run this in the console:
// If using Clerk React SDK
const { getToken } = window.Clerk;
const token = await getToken();
console.log(token);
  1. 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)
Token Expiration

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

  1. 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
  1. User signs in and authorizes your app

  2. Clerk redirects back with an authorization code:

https://your-app.com/callback?code=AUTH_CODE
  1. 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']
  1. 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:

  1. Contact PlanOps support to register your application
  2. Provide your redirect URI(s)
  3. Receive your client_id and client_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

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