API Keys
Create and manage API keys for server-to-server authentication
API keys enable server-to-server communication with the Synoveo API. They are used by the WordPress plugin, custom integrations, and background services.
Overview
An API key consists of two parts:
| Component | Format | Example |
|---|---|---|
| Client ID | syncid_{user_id}_{timestamp}_{name} | syncid_570_1703030400000_wordpress |
| Client Secret | 48 bytes, base64-encoded | a3J5cHRvZ3JhcGhp... |
Security: The client secret is shown only once during creation. Store it securely in environment variables.
Creating API Keys
Via Dashboard
- Navigate to Dashboard → Developer
- Click Create API Key
- Configure the key:
- Name: Descriptive identifier (e.g., "Production WordPress")
- Location: Select the Google Business location to connect
- Domain: (Optional) Restrict to specific domain
- Click Create
- Copy and securely store the credentials
Via API
POST /api/v1/developer/credentials HTTP/1.1
Host: api.synoveo.com
Authorization: Bearer <user_jwt_token>
Content-Type: application/json
{
"name": "My WordPress Site",
"business_id": 123,
"assigned_location_id": "locations/456789",
"primary_domain": "example.com",
"allowed_domains": ["staging.example.com"]
}Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Descriptive name for the API key |
business_id | number | Yes | ID of the business |
assigned_location_id | string | Yes | Google location resource name |
primary_domain | string | No | Primary domain for CORS validation |
allowed_domains | array | No | Additional allowed domains |
Response:
{
"status": "ok",
"data": {
"client_id": "syncid_570_1703030400000_my_wordpress_site",
"client_secret": "a3J5cHRvZ3JhcGhpY2FsbHlfc2VjdXJlX3JhbmRvbV9ieXRlcw==",
"service_client_id": 5432,
"name": "My WordPress Site",
"plan": "pro",
"business_id": 123,
"business_name": "My Business",
"assigned_location_id": "locations/456789",
"primary_domain": "example.com",
"allowed_domains": ["example.com", "www.example.com", "staging.example.com"],
"created_at": "2025-01-15T10:30:00Z",
"warning": "Save this secret securely. It will not be shown again."
}
}Using API Keys
Step 1: Exchange for JWT Token
Use the OAuth2 client credentials flow to exchange your API key for a JWT token:
curl -X POST https://api.synoveo.com/api/v1/auth/token \
-H "Content-Type: application/json" \
-d '{
"grant_type": "client_credentials",
"client_id": "syncid_570_1703030400000_wordpress",
"client_secret": "your_base64_secret"
}'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..."
}
}Step 2: Use the Token
Include the JWT token in API requests:
curl https://api.synoveo.com/api/v1/google-business/locations \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."Token Caching
Important: Cache tokens and reuse them until they expire. Don't request a new token for every API call.
| Token | TTL | Recommended Refresh |
|---|---|---|
| Access Token | 90 days | 5 minutes before expiration |
| Refresh Token | 30 days | When access token expires |
Example Token Cache (Node.js)
let cachedToken = null
let tokenExpiresAt = null
async function getToken() {
// Return cached token if still valid (with 5 min buffer)
if (cachedToken && tokenExpiresAt > Date.now() + 300000) {
return cachedToken
}
const response = await fetch('https://api.synoveo.com/api/v1/auth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
grant_type: 'client_credentials',
client_id: process.env.SYNOVEO_CLIENT_ID,
client_secret: process.env.SYNOVEO_CLIENT_SECRET
})
})
const { data } = await response.json()
cachedToken = data.access_token
tokenExpiresAt = Date.now() + (data.expires_in * 1000)
return cachedToken
}Managing API Keys
List API Keys
GET /api/v1/developer/credentials HTTP/1.1
Host: api.synoveo.com
Authorization: Bearer <user_jwt_token>Response:
{
"status": "ok",
"data": {
"credentials": [
{
"id": 5432,
"client_id": "syncid_570_1703030400000_wordpress",
"name": "My WordPress Site",
"assigned_location_id": "locations/456789",
"primary_domain": "example.com",
"created_at": "2025-01-15T10:30:00Z",
"last_used_at": "2025-01-20T14:22:00Z"
}
]
}
}Check Connection Status
GET /api/v1/api-keys/{client_id}/status HTTP/1.1
Host: api.synoveo.com
Authorization: Bearer <user_jwt_token>Response:
{
"status": "ok",
"data": {
"connection_status": "online",
"last_heartbeat_at": "2025-01-20T14:22:00Z",
"plugin_version": "1.2.0",
"wp_version": "6.4.2",
"php_version": "8.2.0"
}
}| Status | Description |
|---|---|
online | Heartbeat within last 2 hours |
idle | Heartbeat within last 24 hours |
offline | No heartbeat for 24+ hours |
Revoke API Key
DELETE /api/v1/developer/credentials/{client_id} HTTP/1.1
Host: api.synoveo.com
Authorization: Bearer <user_jwt_token>Warning: Revoking an API key immediately invalidates all tokens issued with it.
WordPress Plugin Integration
The Synoveo WordPress plugin handles API key authentication automatically:
- Enter
client_idandclient_secretin plugin settings - Plugin exchanges credentials for JWT token
- Token is cached in WordPress options
- Auto-refresh before expiration
- Heartbeat sent every hour to monitor connection
Heartbeat Endpoint
The plugin sends periodic heartbeats:
POST /api/v1/api-keys/heartbeat HTTP/1.1
Host: api.synoveo.com
Authorization: Bearer <service_jwt_token>
Content-Type: application/json
{
"plugin_version": "1.2.0",
"wp_version": "6.4.2",
"php_version": "8.2.0",
"detected_plugins": ["yoast-seo", "woocommerce"]
}Response:
{
"status": "ok",
"data": {
"received": true,
"next_heartbeat_in": 3600
}
}Validate Credentials
Validate API credentials without issuing a token (useful for WordPress setup):
POST /api/v1/auth/validate-service-credentials HTTP/1.1
Host: api.synoveo.com
Content-Type: application/json
{
"client_id": "syncid_570_1703030400000_wordpress",
"client_secret": "your_base64_secret",
"site_domain": "example.com"
}Response:
{
"status": "ok",
"data": {
"valid": true,
"user": {
"id": 570,
"email": "user@example.com",
"name": "John Doe"
},
"business": {
"id": 123,
"name": "My Business"
},
"location": {
"id": "locations/456789",
"name": "Downtown Location",
"address": "123 Main St, City, State 12345",
"is_verified": true
},
"plan": "pro",
"plan_display_name": "Professional",
"capabilities": {
"sync": true,
"posts": true,
"reviews": true
}
}
}Deactivated API Keys
API keys can be deactivated without deletion:
{
"status": "error",
"error": {
"code": "AUTH_INSUFFICIENT_PERMISSIONS",
"message": "API key has been deactivated",
"details": {
"deactivation_reason": "billing_issue",
"deactivated_at": "2025-01-15T10:00:00Z",
"upgrade_url": "https://app.synoveo.com/dashboard/billing"
}
}
}| Reason | Description |
|---|---|
billing_issue | Payment failed or subscription cancelled |
plan_downgrade | Location limit exceeded after downgrade |
security_concern | Flagged for suspicious activity |
user_requested | Manually deactivated by user |
Security Best Practices
- Environment Variables - Never hardcode secrets in source code
- Domain Binding - Restrict keys to specific domains
- Location Scoping - Each key accesses only one location
- Regular Rotation - Rotate keys periodically
- Monitor Usage - Check
last_used_atfor suspicious activity - Revoke Unused - Delete keys no longer in use
SDK Usage
The @synoveo/sdk handles API key authentication automatically:
import { SynoveoClient } from '@synoveo/sdk'
const client = new SynoveoClient({
clientId: process.env.SYNOVEO_CLIENT_ID,
clientSecret: process.env.SYNOVEO_CLIENT_SECRET,
})
// Token management is automatic
const locations = await client.locations.list()See SDK Documentation for more details.