External Publications API
Token-based API for external platforms to integrate Spokegrid's powerful editor and publication system.
Secure
Two-tier token system with domain whitelisting and rate limiting.
Multi-Destination
Publish to Git, cloud storage, and webhooks simultaneously.
Fast
Event-driven caching with 1-hour TTL for optimal performance.
Base URL
https://api.spokegrid.com/apiAuthentication
The API uses a two-tier authentication system for maximum security:
API Token
Server-side only- Long-lived (configurable expiration)
- Scoped permissions (read, publish)
- Rate limited per token
Session Token
Browser-safe- Short-lived (24 hours)
- Single user, single article
- Safe for URL parameters
Authentication Header
Authorization: Bearer YOUR_API_TOKEN
Never Expose API Tokens
API tokens are sensitive credentials. Always create editor sessions from your server, never expose tokens in client-side code.
Publication Management
Configure where and how your articles are published. These endpoints require CMS authentication (cookie-based).
/publications/configsCreate a new publication configuration
Request Body
{
"name": "Production Blog",
"site_id": "optional_site_id",
"git_enabled": true,
"git_repo_id": "repo_id",
"git_directory": "content/articles",
"git_format": "mdx",
"git_use_pr": false,
"cloud_enabled": true,
"webhook_enabled": true,
"webhook_url": "https://your-site.com/webhooks",
"webhook_secret": "your_secret",
"external_access_enabled": true,
"allowed_domains": ["*.example.com"]
}/publications/configsList all publication configurations for the authenticated user
/publications/tokensCreate an API token for external access
Request Body
{
"publication_config_id": "config_uuid",
"name": "Production API Token",
"scopes": ["read", "publish"],
"allowed_domains": ["*.example.com"],
"rate_limit_per_hour": 1000,
"expires_at": "2026-12-31T23:59:59Z"
}Token Shown Once
The full token is only returned once during creation. Store it securely.
External Editor Sessions
Create short-lived editor sessions for your users. These endpoints use API token authentication.
/external/publications/sessionsCreate a short-lived editor session for an external user
Request Headers
Authorization: Bearer YOUR_API_TOKEN Content-Type: application/json
Request Body
{
"external_user_id": "user_123",
"external_user_email": "editor@example.com",
"external_user_name": "Jane Doe",
"external_user_metadata": {
"role": "editor",
"department": "marketing"
},
"article_id": "optional_existing_article_id"
}Response (200 OK)
{
"id": "session_uuid",
"session_token": "secure_random_token",
"external_user_id": "user_123",
"expires_at": "2025-12-19T12:00:00Z",
"created_at": "2025-12-18T12:00:00Z"
}Next Step
Redirect your user to: https://cms.spokegrid.com/publications/editor/{session_token}
/external/publications/sessions/{session_token}/verifyValidate a session token. Used internally by CMS frontend when loading the editor.
/external/publications/sessions/{session_token}/publishPublish an article from an editor session. Automatically distributes to enabled destinations.
Request Body
{
"article_id": "unique_article_identifier",
"title": "My Amazing Article",
"slug": "my-amazing-article",
"content": "# Markdown content\n\nFull article...",
"format": "mdx",
"metadata": {
"title": "My Amazing Article",
"excerpt": "Brief description",
"featured_image": "https://example.com/image.jpg",
"tags": ["tech", "tutorial"],
"categories": ["development"],
"seo_title": "Custom SEO Title",
"seo_description": "Meta description",
"custom_fields": {}
}
}Response (200 OK)
{
"id": "publication_target_uuid",
"article_id": "unique_article_identifier",
"article_title": "My Amazing Article",
"article_slug": "my-amazing-article",
"published_to_git": true,
"git_commit_sha": "a1b2c3d4...",
"published_to_cloud": true,
"webhook_delivered": true,
"published_at": "2025-12-18T14:30:00Z",
"version": 1
}Publishing Destinations
- Git: Commits to repository or creates PR
- Cloud: Stores in database for API retrieval
- Webhook: Sends signed POST to your endpoint
Reading Articles
Retrieve published articles via API. All read endpoints support heavy caching with event-driven invalidation.
/external/publications/articles?limit=50&offset=0Retrieve all published articles with pagination
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
limit | integer | 50 | Max articles (1-100) |
offset | integer | 0 | Pagination offset |
/external/publications/articles/{article_id}Get a specific article by its unique ID
/external/publications/articles/by-slug/{slug}Get an article by its URL-friendly slug (useful for frontend routing)
Webhooks
Receive real-time notifications when articles are published. Webhooks are delivered with HMAC signatures for security.
Webhook Payload
POST https://your-webhook-url.com/endpoint
Headers:
X-Webhook-Signature: sha256=a1b2c3d4...
X-Webhook-Timestamp: 2025-12-18T14:30:00Z
Content-Type: application/json
Body:
{
"event": "publish",
"article": {
"id": "uuid",
"title": "My Amazing Article",
"slug": "my-amazing-article",
"content": "Full content...",
"format": "mdx",
"metadata": {},
"author_name": "Jane Doe",
"author_email": "editor@example.com",
"published_at": "2025-12-18T14:30:00Z",
"version": 1
},
"publication_config_id": "config_uuid",
"timestamp": "2025-12-18T14:30:00Z"
}Verify Webhook Signature
Always verify the HMAC signature to ensure webhooks are from Spokegrid.
Signature Verification
const crypto = require('crypto');
function verifyWebhook(payload, signature, secret) {
const expectedSig = crypto
.createHmac('sha256', secret)
.update(JSON.stringify(payload))
.digest('hex');
return signature === `sha256=${expectedSig}`;
}
app.post('/webhooks/articles', (req, res) => {
const sig = req.headers['x-webhook-signature'];
const isValid = verifyWebhook(req.body, sig, WEBHOOK_SECRET);
if (!isValid) {
return res.status(401).send('Invalid signature');
}
const { event, article } = req.body;
console.log(`Received ${event}: ${article.title}`);
res.status(200).send('OK');
});Automatic Retries
Failed webhooks are automatically retried up to 3 times with exponential backoff (5min, 10min, 15min).
Reading Articles
Retrieve published articles from your publication configuration. These endpoints use API token authentication.
/external/publications/articlesList published articles with pagination
Query Parameters
limit: 50 (max 100, default 50) offset: 0 (default 0)
Response (200 OK)
[
{
"id": "article_uuid",
"title": "My Amazing Article",
"slug": "my-amazing-article",
"content": "Full markdown content...",
"format": "mdx",
"metadata": {
"tags": ["tech", "tutorial"],
"categories": ["development"]
},
"author_name": "Jane Doe",
"author_email": "editor@example.com",
"published_at": "2025-12-18T14:30:00Z",
"version": 1
}
]/external/publications/articles/{article_id}Get a specific published article by its ID
/external/publications/articles/by-slug/{slug}Get a published article by its URL slug
/external/publications/analyticsGet publication analytics and statistics
Query Parameters
days: 30 (default 30, max 365)
Response (200 OK)
{
"config_id": "config_uuid",
"period_days": 30,
"total_publications": 150,
"successful_publications": 145,
"failed_publications": 5,
"git_publications": 140,
"cloud_publications": 145,
"webhook_deliveries": 142,
"average_processing_time_ms": 1250,
"error_rate": 0.033,
"recent_errors": [
{
"timestamp": "2025-12-18T10:30:00Z",
"error": "Git push failed: repository not found"
}
],
"daily_stats": [
{
"date": "2025-12-18",
"publications": 12,
"successful": 11,
"failed": 1
}
]
}Security Features
Token Hashing
All tokens are SHA-256 hashed before storage. Full token only shown once.
Domain Whitelisting
Configure allowed domains per token. Supports wildcards (*.example.com).
Rate Limiting
Configurable per-token rate limits. Returns HTTP 429 when exceeded.
Audit Logging
Complete audit trail with IP, user agent, and referrer for all operations.
Rate Limits
Rate limits are configurable per API token. Default is 100 requests per hour.
Rate Limit Headers
X-RateLimit-Limit: 100 X-RateLimit-Remaining: 87 X-RateLimit-Reset: 1703001600
Rate Limit Exceeded
When exceeded, API returns HTTP 429 with Retry-After header.
Error Codes
| Status | Meaning | Common Causes |
|---|---|---|
200 | OK | Request successful |
400 | Bad Request | Invalid request body |
401 | Unauthorized | Invalid or expired token |
403 | Forbidden | Domain not whitelisted |
404 | Not Found | Resource doesn't exist |
429 | Too Many Requests | Rate limit exceeded |
500 | Server Error | Unexpected error |
Complete Integration Example
1. Server: Create Editor Session
export async function POST(request) {
const { userId } = await request.json();
const user = await getUserById(userId);
const response = await fetch(
'https://api.spokegrid.com/api/external/publications/sessions',
{
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.SPOKEGRID_API_TOKEN}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
external_user_id: user.id,
external_user_email: user.email,
external_user_name: user.name
})
}
);
const session = await response.json();
return Response.json({ sessionToken: session.session_token });
}2. Frontend: Redirect to Editor
async function openEditor() {
const res = await fetch('/api/create-editor-session', {
method: 'POST',
body: JSON.stringify({ userId: currentUser.id })
});
const { sessionToken } = await res.json();
window.location.href =
`https://cms.spokegrid.com/publications/editor/${sessionToken}`;
}3. Backend: Receive Webhook
app.post('/webhooks/spokegrid', async (req, res) => {
const sig = req.headers['x-webhook-signature'];
const payload = JSON.stringify(req.body);
const expectedSig = crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(payload)
.digest('hex');
if (sig !== `sha256=${expectedSig}`) {
return res.status(401).send('Invalid signature');
}
const { event, article } = req.body;
if (event === 'publish') {
await Article.create({
externalId: article.id,
title: article.title,
content: article.content,
publishedAt: article.published_at
});
}
res.status(200).send('OK');
});Need Help?
Contact support for assistance with integration, custom rate limits, or enterprise features.