Skip to main content
JWT (JSON Web Token) authentication provides secure, time-limited access to the Open Wearables API. This method is designed for interactive applications where users log in with credentials.

Overview

JWT tokens are short-lived credentials issued after successful login. They include:
  • Access Token: Short-lived token (default: 60 minutes) for API requests
  • Refresh Token: Long-lived token for obtaining new access tokens
  • Token Type: Always bearer
  • Expiration: Time until access token expires (in seconds)

Token Structure

Access tokens are JWT-encoded strings containing:
{
  "exp": 1709820600,  // Expiration timestamp (Unix epoch)
  "sub": "550e8400-e29b-41d4-a716-446655440000"  // Subject (developer ID)
}
Tokens are signed using HS256 algorithm with a server-side secret. Never attempt to decode or forge tokens client-side.

Authentication Flow

1

Login with credentials

Send email and password to /api/v1/auth/login to receive access and refresh tokens.
2

Include token in requests

Add the access token to the Authorization header as Bearer {token} for all API requests.
3

Refresh when expired

When the access token expires (401 error), use the refresh token to get a new access token without re-authenticating.
4

Logout when done

Call /api/v1/auth/logout and clear stored tokens on the client side.

API Endpoints

Login

This endpoint uses OAuth2 password flow, which requires application/x-www-form-urlencoded content type and uses the username field for the email address.
username
string
required
Developer email address (OAuth2 spec uses “username” field)
password
string
required
Developer account password
Request Example:
curl -X POST https://api.openwearables.com/api/v1/auth/login \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "[email protected]" \
  -d "password=your-secure-password"
Response (200 OK):
{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MDk4MjA2MDAsInN1YiI6IjU1MGU4NDAwLWUyOWItNDFkNC1hNzE2LTQ0NjY1NTQ0MDAwMCJ9.Xm1j2k3n4l5m6n7o8p9q0r1s2t3u4v5w6x7y8z9a0b",
  "token_type": "bearer",
  "refresh_token": "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6",
  "expires_in": 3600
}
access_token
string
required
JWT access token to use in Authorization header. Valid for the duration specified in expires_in.
token_type
string
required
Always "bearer". Use as Authorization: Bearer {access_token}.
refresh_token
string
Long-lived token to obtain new access tokens. Store securely.
expires_in
integer
Number of seconds until the access token expires (default: 3600 = 1 hour).
Error Response (401 Unauthorized):
{
  "detail": "Incorrect email or password"
}

Get Current Developer

Request Example:
curl https://api.openwearables.com/api/v1/auth/me \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK):
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "email": "[email protected]",
  "first_name": "Jane",
  "last_name": "Developer",
  "created_at": "2026-01-15T08:30:00Z",
  "updated_at": "2026-03-07T10:30:00Z"
}
id
string (uuid)
required
Unique identifier for the developer account.
email
string
required
Developer’s email address.
first_name
string
Developer’s first name.
last_name
string
Developer’s last name.
created_at
string (datetime)
required
ISO 8601 timestamp when the account was created.
updated_at
string (datetime)
required
ISO 8601 timestamp of the last account update.

Update Current Developer

first_name
string
Update the developer’s first name (max 100 characters).
last_name
string
Update the developer’s last name (max 100 characters).
email
string
Update the developer’s email address.
Request Example:
curl -X PATCH https://api.openwearables.com/api/v1/auth/me \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
  -H "Content-Type: application/json" \
  -d '{"first_name": "Jane", "last_name": "Smith"}'
Response (200 OK):
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "email": "[email protected]",
  "first_name": "Jane",
  "last_name": "Smith",
  "created_at": "2026-01-15T08:30:00Z",
  "updated_at": "2026-03-07T11:45:00Z"
}

Logout

Currently, token invalidation is handled client-side. Ensure you clear both access and refresh tokens from storage after logout.
Request Example:
curl -X POST https://api.openwearables.com/api/v1/auth/logout \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK):
{
  "message": "Successfully logged out"
}

Token Refresh

Coming Soon: The refresh token endpoint is planned but not yet implemented. Currently, users must re-authenticate when tokens expire.
The refresh endpoint will allow exchanging a refresh token for a new access token:
curl -X POST https://api.openwearables.com/api/v1/auth/refresh \
  -H "Content-Type: application/json" \
  -d '{"refresh_token": "your-refresh-token"}'

Using JWT Tokens

Include the access token in the Authorization header using the Bearer scheme:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
import requests

class OpenWearablesClient:
    def __init__(self, access_token: str):
        self.access_token = access_token
        self.base_url = "https://api.openwearables.com/api/v1"
    
    def _headers(self) -> dict:
        return {"Authorization": f"Bearer {self.access_token}"}
    
    def get_current_developer(self) -> dict:
        response = requests.get(
            f"{self.base_url}/auth/me",
            headers=self._headers()
        )
        response.raise_for_status()
        return response.json()
    
    def list_api_keys(self) -> list[dict]:
        response = requests.get(
            f"{self.base_url}/api-keys",
            headers=self._headers()
        )
        response.raise_for_status()
        return response.json()

# Usage
client = OpenWearablesClient(access_token="your-jwt-token")
developer = client.get_current_developer()
api_keys = client.list_api_keys()

Handling Token Expiration

Implement automatic token refresh to maintain seamless user sessions:
token_manager.py
import time
import requests
from typing import Optional

class TokenManager:
    """Manage JWT tokens with automatic refresh."""
    
    def __init__(self, email: str, password: str):
        self.email = email
        self.password = password
        self.base_url = "https://api.openwearables.com/api/v1"
        
        self.access_token: Optional[str] = None
        self.refresh_token: Optional[str] = None
        self.expires_at: Optional[float] = None
    
    def login(self) -> None:
        """Perform initial login."""
        response = requests.post(
            f"{self.base_url}/auth/login",
            data={
                "username": self.email,
                "password": self.password
            }
        )
        response.raise_for_status()
        
        data = response.json()
        self.access_token = data["access_token"]
        self.refresh_token = data["refresh_token"]
        self.expires_at = time.time() + data["expires_in"]
    
    def is_token_valid(self) -> bool:
        """Check if current token is still valid (with 5 min buffer)."""
        if not self.access_token or not self.expires_at:
            return False
        return time.time() < (self.expires_at - 300)  # 5 min buffer
    
    def refresh_access_token(self) -> None:
        """Refresh the access token using refresh token."""
        # Note: This endpoint is not yet implemented
        # When available, it will look like this:
        response = requests.post(
            f"{self.base_url}/auth/refresh",
            json={"refresh_token": self.refresh_token}
        )
        response.raise_for_status()
        
        data = response.json()
        self.access_token = data["access_token"]
        self.expires_at = time.time() + data["expires_in"]
    
    def get_valid_token(self) -> str:
        """Get a valid access token, refreshing if necessary."""
        if not self.is_token_valid():
            if self.refresh_token:
                try:
                    self.refresh_access_token()
                except Exception:
                    # Refresh failed, re-login
                    self.login()
            else:
                self.login()
        
        return self.access_token
    
    def logout(self) -> None:
        """Logout and clear tokens."""
        if self.access_token:
            try:
                requests.post(
                    f"{self.base_url}/auth/logout",
                    headers={"Authorization": f"Bearer {self.access_token}"}
                )
            except Exception:
                pass  # Logout failed, but clear tokens anyway
        
        self.access_token = None
        self.refresh_token = None
        self.expires_at = None

# Usage
token_manager = TokenManager(
    email="[email protected]",
    password="your-password"
)

# Get a valid token (automatically refreshes if needed)
access_token = token_manager.get_valid_token()

# Use the token
response = requests.get(
    "https://api.openwearables.com/api/v1/auth/me",
    headers={"Authorization": f"Bearer {access_token}"}
)

# Logout when done
token_manager.logout()

Best Practices

Web Applications:
  • Use httpOnly cookies for refresh tokens (prevents XSS attacks)
  • Store access tokens in memory or sessionStorage
  • Never store tokens in localStorage if XSS is a concern
Mobile Applications:
  • Use secure storage (Keychain on iOS, Keystore on Android)
  • Never store tokens in plain text
Server Applications:
  • Use environment variables
  • Prefer API keys over JWT for server-to-server
  • Implement automatic token refresh
  • Add a 5-minute buffer before actual expiration
  • Handle 401 errors by attempting refresh
  • Re-authenticate if refresh fails
  • Show user-friendly “session expired” messages
  • Call the logout endpoint
  • Clear all stored tokens (access + refresh)
  • Redirect to login page
  • Cancel any pending requests
  • Never send tokens over HTTP
  • Validate SSL certificates
  • Use certificate pinning in mobile apps
  • Implement CSRF protection for web apps
  • Use short expiration times (15-60 minutes)
  • Monitor for suspicious activity
  • Rotate refresh tokens periodically

Error Responses

Status CodeErrorDescription
401UnauthorizedInvalid credentials or expired token
403ForbiddenValid token but insufficient permissions
422Validation ErrorInvalid request format (check content-type)
429Rate LimitedToo many login attempts
500Server ErrorInternal server error
Common Error Responses:
// Invalid credentials
{
  "detail": "Incorrect email or password"
}

// Expired token
{
  "detail": "Could not validate credentials"
}

// Missing token
{
  "detail": "Not authenticated"
}

Complete Example: React Authentication

useAuth.ts
import { useState, useEffect } from 'react';

interface TokenData {
  access_token: string;
  refresh_token: string;
  expires_in: number;
}

interface Developer {
  id: string;
  email: string;
  first_name: string | null;
  last_name: string | null;
}

export function useAuth() {
  const [accessToken, setAccessToken] = useState<string | null>(null);
  const [developer, setDeveloper] = useState<Developer | null>(null);
  const [loading, setLoading] = useState(true);

  const baseUrl = 'https://api.openwearables.com/api/v1';

  // Check for existing token on mount
  useEffect(() => {
    const token = sessionStorage.getItem('access_token');
    if (token) {
      setAccessToken(token);
      fetchDeveloper(token);
    } else {
      setLoading(false);
    }
  }, []);

  const fetchDeveloper = async (token: string) => {
    try {
      const response = await fetch(`${baseUrl}/auth/me`, {
        headers: { 'Authorization': `Bearer ${token}` }
      });
      
      if (response.ok) {
        const dev = await response.json();
        setDeveloper(dev);
      } else {
        // Token invalid, clear it
        logout();
      }
    } catch (error) {
      console.error('Failed to fetch developer:', error);
    } finally {
      setLoading(false);
    }
  };

  const login = async (email: string, password: string): Promise<boolean> => {
    try {
      const response = await fetch(`${baseUrl}/auth/login`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
        body: new URLSearchParams({ username: email, password })
      });

      if (!response.ok) {
        const error = await response.json();
        throw new Error(error.detail || 'Login failed');
      }

      const tokenData: TokenData = await response.json();
      
      // Store tokens
      sessionStorage.setItem('access_token', tokenData.access_token);
      sessionStorage.setItem('refresh_token', tokenData.refresh_token);
      
      setAccessToken(tokenData.access_token);
      await fetchDeveloper(tokenData.access_token);
      
      return true;
    } catch (error) {
      console.error('Login error:', error);
      return false;
    }
  };

  const logout = async () => {
    if (accessToken) {
      try {
        await fetch(`${baseUrl}/auth/logout`, {
          method: 'POST',
          headers: { 'Authorization': `Bearer ${accessToken}` }
        });
      } catch (error) {
        console.error('Logout error:', error);
      }
    }

    // Clear everything
    sessionStorage.removeItem('access_token');
    sessionStorage.removeItem('refresh_token');
    setAccessToken(null);
    setDeveloper(null);
  };

  return {
    accessToken,
    developer,
    loading,
    isAuthenticated: !!developer,
    login,
    logout
  };
}

API Key Authentication

Learn about long-lived credentials for server integration

Authentication Overview

Compare authentication methods and security best practices

Build docs developers (and LLMs) love