JWT Tokens
Understanding JWT token structure, claims, and validation
All Synoveo API authentication uses RS256-signed JWT tokens. This page explains the token structure and how to validate tokens.
Token Types
| Type | Scope Claim | Purpose | Default TTL |
|---|---|---|---|
| Access Token | user or service | API requests | 90 days (service) / 7 days (user) |
| Refresh Token | service | Get new access tokens | 30 days |
Token Structure
Access Token (Service Scope)
Issued when exchanging API key credentials:
{
"alg": "RS256",
"typ": "JWT"
}Payload:
{
"scope": "service",
"plan": "pro",
"permissions": ["business.read", "business.write"],
"uid": 570,
"sub": "syncid_570_1703030400000_my_app",
"iss": "https://api.synoveo.com",
"aud": "https://api.synoveo.com",
"iat": 1703030400,
"exp": 1710806400
}| Claim | Type | Description |
|---|---|---|
scope | string | "service" for API keys, "user" for dashboard |
plan | string | User's subscription plan (lite, solo, pro, business) |
permissions | array | Granted permissions (business.read, business.write) |
uid | number | Numeric user ID (owner of the API key) |
sub | string | Subject - the client_id for service tokens |
iss | string | Issuer URL |
aud | string | Audience URL |
iat | number | Issued at (Unix timestamp) |
exp | number | Expiration (Unix timestamp) |
Access Token (User Scope)
Issued after Google OAuth authentication:
{
"scope": "user",
"plan": "pro",
"permissions": [],
"uid": 570,
"sub": "570",
"iss": "https://api.synoveo.com",
"aud": "https://api.synoveo.com",
"iat": 1703030400,
"exp": 1703635200
}Note: User-scoped tokens have empty
permissionsarray but are granted full access.
Refresh Token
{
"typ": "refresh",
"scope": "service",
"permissions": ["business.read", "business.write"],
"uid": 570,
"sub": "syncid_570_1703030400000_my_app",
"iss": "https://api.synoveo.com",
"aud": "https://api.synoveo.com",
"iat": 1703030400,
"exp": 1705622400
}The typ: "refresh" claim distinguishes refresh tokens from access tokens.
Token Signing
Tokens are signed using RS256 (RSA Signature with SHA-256):
- Algorithm: RS256
- Key Type: RSA 2048-bit or higher
- Private Key: Used for signing (server-side only)
- Public Key: Used for verification (can be shared)
Obtaining the Public Key
For token verification in your application, contact support for the public key or use the JWKS endpoint (if available).
Token Validation
When validating tokens, verify:
- Signature - RS256 signature matches using public key
- Issuer -
issclaim equalshttps://api.synoveo.com - Audience -
audclaim equalshttps://api.synoveo.com - Expiration -
expclaim is in the future - Scope -
scopeclaim matches expected value
Example Validation (Node.js)
import jwt from 'jsonwebtoken'
const publicKey = process.env.JWT_PUBLIC_KEY
function validateToken(token) {
try {
const decoded = jwt.verify(token, publicKey, {
algorithms: ['RS256'],
issuer: 'https://api.synoveo.com',
audience: 'https://api.synoveo.com'
})
return { valid: true, payload: decoded }
} catch (error) {
return { valid: false, error: error.message }
}
}Using Tokens
Include the access token in the Authorization header:
GET /api/v1/google-business/locations HTTP/1.1
Host: api.synoveo.com
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...Token Refresh Flow
When access tokens expire, use the refresh token to obtain a new one:
POST /api/v1/auth/token HTTP/1.1
Host: api.synoveo.com
Content-Type: application/json
{
"grant_type": "refresh_token",
"refresh_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
}Response:
{
"status": "ok",
"data": {
"token_type": "Bearer",
"scope": "service",
"plan": "pro",
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"expires_in": 7776000,
"permissions": ["business.read", "business.write"],
"refresh_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
}
}Scope Differences
Service Scope (scope: "service")
- Issued to API keys
- Permissions explicitly listed in token
- Access restricted to assigned location
- Plan inherited from key owner
User Scope (scope: "user")
- Issued after Google OAuth
- Full access to all user resources
- Permissions array is empty (full access implied)
- Can access all locations owned by user
Common Errors
| Error | Cause | Solution |
|---|---|---|
AUTH_INVALID_TOKEN | Malformed or invalid signature | Check token format and signature |
AUTH_TOKEN_EXPIRED | Token past expiration | Use refresh token to get new access token |
AUTH_MISSING_TOKEN | No Authorization header | Include Authorization: Bearer <token> |
AUTH_INSUFFICIENT_PERMISSIONS | Missing required permission | Request additional permissions |
SDK Usage
The @synoveo/sdk handles token management automatically:
import { SynoveoClient } from '@synoveo/sdk'
const client = new SynoveoClient({
clientId: 'syncid_570_...',
clientSecret: 'your_secret',
// Tokens are managed automatically
hooks: {
onTokenRefresh: (token) => {
console.log('Token refreshed, expires:', token.expiresAt)
}
}
})See SDK Documentation for complete token management features.