API Authentication
Learn how to authenticate with the Petal API.
Authentication Methods
The Petal API supports two authentication methods:
- Bearer Token - For user-authenticated requests
- API Key - For server-to-server validation (limited endpoints)
Bearer Token Authentication
Most endpoints require a Bearer token in the Authorization header:
Authorization: Bearer <access_token>
Obtaining Tokens
Login
POST /api/v1/auth/login
Content-Type: application/json
{
"email": "user@example.com",
"password": "your-password"
}
Response:
{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"refresh_token": "v1.MjAyNi0wMS0yN...",
"expires_at": "2026-01-27T16:30:00Z",
"user": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"email": "user@example.com",
"full_name": "John Doe"
}
}
Token Lifetime
| Token Type | Lifetime |
|---|---|
| Access Token | 1 hour |
| Refresh Token | 7 days |
Refreshing Tokens
Before your access token expires, refresh it:
POST /api/v1/auth/refresh
Content-Type: application/json
{
"refresh_token": "v1.MjAyNi0wMS0yN..."
}
Response:
{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"refresh_token": "v1.MjAyNi0wMS0yOC...",
"expires_at": "2026-01-27T17:30:00Z"
}
tip
Refresh tokens are rotated on each use. Always store the new refresh token.
Sign Up
Create a new account:
POST /api/v1/auth/signup
Content-Type: application/json
{
"email": "newuser@example.com",
"password": "secure-password",
"full_name": "Jane Smith"
}
Response (immediate access):
{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"refresh_token": "v1.MjAyNi0wMS0yN...",
"expires_at": "2026-01-27T16:30:00Z",
"user": {
"id": "550e8400-e29b-41d4-a716-446655440001",
"email": "newuser@example.com",
"full_name": "Jane Smith"
}
}
API Key Authentication
For validating API keys (server-to-server), no Bearer token is required:
POST /api/v1/metrics/api-key/validate
Content-Type: application/json
{
"api_key": "petal_live_xxxxxxxxxxxxx"
}
Response:
{
"valid": true,
"plan": "standard",
"source": "new",
"features": {
"csv_export": true,
"api_access": true
},
"subscription_active": true
}
Rate Limited
API key validation is rate limited to 60 requests/minute to prevent abuse.
Error Responses
Invalid Credentials
{
"error": "Invalid email or password",
"code": "INVALID_CREDENTIALS"
}
Expired Token
{
"error": "Token has expired",
"code": "TOKEN_EXPIRED"
}
Invalid Token
{
"error": "Invalid or malformed token",
"code": "INVALID_TOKEN"
}
Code Examples
Python
import requests
from datetime import datetime, timedelta
class PetalClient:
def __init__(self, email, password):
self.base_url = 'https://petal.tech/api/v1'
self.access_token = None
self.refresh_token = None
self.expires_at = None
self._login(email, password)
def _login(self, email, password):
response = requests.post(f'{self.base_url}/auth/login', json={
'email': email,
'password': password
})
response.raise_for_status()
data = response.json()
self._update_tokens(data)
def _update_tokens(self, data):
self.access_token = data['access_token']
self.refresh_token = data['refresh_token']
self.expires_at = datetime.fromisoformat(data['expires_at'].replace('Z', '+00:00'))
def _ensure_valid_token(self):
if datetime.now(self.expires_at.tzinfo) >= self.expires_at - timedelta(minutes=5):
self._refresh()
def _refresh(self):
response = requests.post(f'{self.base_url}/auth/refresh', json={
'refresh_token': self.refresh_token
})
response.raise_for_status()
self._update_tokens(response.json())
def get(self, endpoint):
self._ensure_valid_token()
response = requests.get(
f'{self.base_url}{endpoint}',
headers={'Authorization': f'Bearer {self.access_token}'}
)
response.raise_for_status()
return response.json()
# Usage
client = PetalClient('user@example.com', 'password')
status = client.get('/subscription/status')
print(f"Plan: {status['subscription']['plan']}")
JavaScript
class PetalClient {
constructor(email, password) {
this.baseUrl = 'https://petal.tech/api/v1';
this.tokens = null;
this.loginPromise = this._login(email, password);
}
async _login(email, password) {
const response = await fetch(`${this.baseUrl}/auth/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password })
});
this.tokens = await response.json();
}
async _ensureToken() {
await this.loginPromise;
const expiresAt = new Date(this.tokens.expires_at);
if (Date.now() >= expiresAt - 5 * 60 * 1000) {
await this._refresh();
}
}
async _refresh() {
const response = await fetch(`${this.baseUrl}/auth/refresh`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ refresh_token: this.tokens.refresh_token })
});
this.tokens = await response.json();
}
async get(endpoint) {
await this._ensureToken();
const response = await fetch(`${this.baseUrl}${endpoint}`, {
headers: { 'Authorization': `Bearer ${this.tokens.access_token}` }
});
return response.json();
}
}
// Usage
const client = new PetalClient('user@example.com', 'password');
const status = await client.get('/subscription/status');
console.log(`Plan: ${status.subscription.plan}`);
Security Best Practices
- Store tokens securely - Use secure storage (keychain, credential manager)
- Never log tokens - Avoid logging access or refresh tokens
- Use HTTPS only - All API calls must use HTTPS
- Rotate tokens - Refresh before expiration
- Handle errors - Implement proper error handling for auth failures