Building Custom Integrations
Build custom integrations with BlackOps Center using our REST API, webhooks, and extensibility points.
BlackOps Center is built to be extended. Whether you're connecting to your CRM, building a mobile app, or automating workflows with Zapier, our API and webhook system make it possible.
Integration Approaches
1. REST API
Use Case: Pull data from BlackOps Center or push data to it programmatically
Examples:
- Fetch blog posts for a mobile app
- Create reservoir items from external tools
- Sync content to a CMS
- Build custom analytics dashboards
Authentication: API keys (bearer token)
2. Webhooks
Use Case: React to events in real-time without polling
Examples:
- Notify Slack when content is published
- Update CRM when user subscribes
- Trigger CI/CD pipeline on content changes
- Sync analytics to data warehouse
Authentication: Signature verification
3. Email Integration
Use Case: Forward content to BlackOps Center via email
Examples:
- Forward newsletters to reservoirs
- Save Gmail starred emails as content
- Email voice memos (via transcription service)
Authentication: Unique reservoir email addresses
4. Browser Extension
Use Case: Capture content while browsing
Examples:
- One-click bookmark to reservoir
- Right-click to save selection
- Automatic capture of starred tweets
Authentication: OAuth or API key
5. MCP (Claude CoWork)
Use Case: Run BlackOps operations directly from Claude via MCP tools
Examples:
- List sites, posts, and notes from chat
- Create and publish drafts without leaving Claude
- Fetch brand voice/context before generating content
- Run tweet workflows across multiple site accounts
Authentication: Bearer token via MCP server environment
Read the Claude CoWork MCP setup guide.
REST API
Base URL
https://blackopscenter.com/api
Authentication
All API requests require an API key passed as a bearer token:
Authorization: Bearer YOUR_API_KEY
Generating API Keys
- Log in to BlackOps Center
- Go to Settings → Developer → API Keys
- Click "Create API Key"
- Name it (e.g., "Mobile App", "Zapier Integration")
- Copy the key immediately (only shown once)
- Store securely
🔒 API Key Security
- Never commit keys to version control
- Use environment variables
- Rotate keys periodically
- Revoke keys when no longer needed
- Use separate keys per integration (easier to revoke)
Common API Endpoints
Reservoirs
# List reservoirs
GET /api/admin/reservoirs?site_id={siteId}
# Get reservoir details
GET /api/admin/reservoirs/{reservoirId}
# Create reservoir
POST /api/admin/reservoirs
{
"name": "React Performance",
"description": "Performance tips and patterns",
"site_id": "xxx"
}
# Add item to reservoir
POST /api/admin/reservoirs/{reservoirId}/items
{
"title": "React 19 useMemo Changes",
"content": "Full content here...",
"url": "https://example.com/post",
"tags": ["react", "performance"]
}
Blog Posts
# List published posts
GET /api/blog/posts?site_id={siteId}&limit=10
# Get single post
GET /api/blog/posts/{slug}
# Create draft
POST /api/admin/posts
{
"title": "Getting Started with Next.js",
"content": "Markdown content...",
"status": "draft",
"site_id": "xxx"
}
# Publish post
PATCH /api/admin/posts/{postId}
{
"status": "published",
"published_at": "2025-02-01T10:00:00Z"
}
Newsletter Subscribers
# List subscribers
GET /api/admin/newsletters/{newsletterId}/subscribers
# Add subscriber
POST /api/admin/newsletters/{newsletterId}/subscribe
{
"email": "user@example.com",
"name": "User Name"
}
# Unsubscribe
POST /api/admin/newsletters/{newsletterId}/unsubscribe
{
"email": "user@example.com"
}
Analytics
# Get site analytics
GET /api/admin/analytics?site_id={siteId}&start_date=2025-01-01&end_date=2025-01-31
# Track custom event
POST /api/track/event
{
"event_name": "custom_cta_click",
"site_id": "xxx",
"properties": {
"cta_location": "sidebar",
"post_slug": "react-performance"
}
}
Error Handling
API returns standard HTTP status codes:
200 OK - Success
201 Created - Resource created
400 Bad Request - Invalid input
401 Unauthorized - Missing/invalid API key
403 Forbidden - No access to resource
404 Not Found - Resource doesn't exist
429 Too Many Requests - Rate limit exceeded
500 Internal Server Error - Server error
# Error response format
{
"error": "Invalid API key",
"code": "INVALID_AUTH",
"details": {
"message": "The API key provided is not valid"
}
}
Rate Limiting
API requests are rate-limited per API key:
- Free Tier: 100 requests/hour
- Pro Tier: 1,000 requests/hour
- Enterprise Tier: 10,000 requests/hour
Rate limit headers:
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 847
X-RateLimit-Reset: 1738368000
Integration Examples
Example 1: Sync Notion Pages to Reservoir
// notion-to-blackops.js
import { Client } from '@notionhq/client'
const notion = new Client({ auth: process.env.NOTION_API_KEY })
const BLACKOPS_API_KEY = process.env.BLACKOPS_API_KEY
const RESERVOIR_ID = 'your-reservoir-id'
async function syncNotionToReservoir() {
// Fetch pages from Notion database
const response = await notion.databases.query({
database_id: process.env.NOTION_DATABASE_ID,
filter: {
property: 'Status',
select: { equals: 'Ready to Sync' }
}
})
for (const page of response.results) {
// Extract content
const title = page.properties.Name.title[0]?.plain_text
const content = await getPageContent(page.id)
// Create reservoir item
await fetch(`https://blackopscenter.com/api/admin/reservoirs/${RESERVOIR_ID}/items`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${BLACKOPS_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
title,
content,
source_type: 'notion',
source_id: page.id,
tags: ['notion', 'synced']
})
})
// Update Notion status
await notion.pages.update({
page_id: page.id,
properties: {
Status: { select: { name: 'Synced' } }
}
})
}
}
// Run daily
setInterval(syncNotionToReservoir, 24 * 60 * 60 * 1000)
Example 2: Slack Notification on Post Publish
// webhook-handler.js (your server)
import { WebhookClient } from '@slack/webhook'
const slackWebhook = new WebhookClient(process.env.SLACK_WEBHOOK_URL)
export async function POST(request) {
const event = await request.json()
if (event.type === 'post.published') {
const post = event.data
await slackWebhook.send({
text: `📝 New blog post published!`,
blocks: [
{
type: 'section',
text: {
type: 'mrkdwn',
text: `*${post.title}*\n${post.excerpt}`
}
},
{
type: 'actions',
elements: [
{
type: 'button',
text: { type: 'plain_text', text: 'View Post' },
url: post.url
}
]
}
]
})
}
return new Response('OK', { status: 200 })
}
Example 3: iOS Shortcut to Add Reservoir Item
# iOS Shortcuts app (pseudo-code)
1. Get URL from Share Sheet
2. Extract metadata (title, image) via web scraping
3. Get text input "Add notes (optional)"
4. Make API request:
POST https://blackopscenter.com/api/admin/reservoirs/{reservoirId}/items
Headers:
Authorization: Bearer {API_KEY}
Body:
{
"url": {URL from Share Sheet},
"title": {Extracted Title},
"notes": {User Notes},
"source_type": "mobile_shortcut",
"tags": ["mobile-saved"]
}
5. Show notification "Saved to reservoir"
Example 4: Zapier Integration
# Zapier Workflow
Trigger: New row in Google Sheets
Action: HTTP POST to BlackOps Center API
Setup:
1. Trigger: New or Updated Row in Google Sheets
- Spreadsheet: Content Ideas
- Worksheet: Ideas
2. Action: Webhooks by Zapier - POST
- URL: https://blackopscenter.com/api/admin/reservoirs/xxx/items
- Headers:
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
- Data:
{
"title": {{Title}},
"content": {{Description}},
"tags": ["google-sheets", "idea"],
"source_type": "zapier"
}
Result: Google Sheets → Automatic reservoir item
Building a Chrome Extension
Manifest v3 Example
// manifest.json
{
"manifest_version": 3,
"name": "BlackOps Center Clipper",
"version": "1.0",
"permissions": [
"activeTab",
"storage"
],
"background": {
"service_worker": "background.js"
},
"action": {
"default_popup": "popup.html",
"default_icon": "icon.png"
},
"commands": {
"save-to-reservoir": {
"suggested_key": {
"default": "Ctrl+Shift+S"
},
"description": "Save to reservoir"
}
}
}
Background Script
// background.js
chrome.commands.onCommand.addListener((command) => {
if (command === 'save-to-reservoir') {
chrome.tabs.query({ active: true, currentWindow: true }, async (tabs) => {
const tab = tabs[0]
// Get API key from storage
const { apiKey } = await chrome.storage.sync.get('apiKey')
// Save current page
await fetch('https://blackopscenter.com/api/admin/reservoirs/xxx/items', {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
title: tab.title,
url: tab.url,
source_type: 'chrome_extension',
tags: ['chrome-saved']
})
})
// Show notification
chrome.notifications.create({
type: 'basic',
iconUrl: 'icon.png',
title: 'Saved to Reservoir',
message: tab.title
})
})
}
})
Best Practices
1. Handle Errors Gracefully
async function createReservoirItem(data) {
try {
const response = await fetch(API_URL, {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
})
if (!response.ok) {
const error = await response.json()
throw new Error(`API Error: ${error.message}`)
}
return await response.json()
} catch (error) {
console.error('Failed to create item:', error)
// Don't crash - queue for retry or notify user
await queueForRetry(data)
}
}
2. Implement Retry Logic
async function apiRequestWithRetry(url, options, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
const response = await fetch(url, options)
if (response.ok) return response
// Retry on 5xx errors
if (response.status >= 500) {
await sleep(Math.pow(2, i) * 1000) // Exponential backoff
continue
}
// Don't retry on 4xx errors
throw new Error(`HTTP ${response.status}`)
} catch (error) {
if (i === maxRetries - 1) throw error
await sleep(Math.pow(2, i) * 1000)
}
}
}
3. Respect Rate Limits
class RateLimitedClient {
constructor(apiKey, requestsPerHour = 1000) {
this.apiKey = apiKey
this.requestsPerHour = requestsPerHour
this.requests = []
}
async request(url, options) {
// Remove requests older than 1 hour
const oneHourAgo = Date.now() - 3600000
this.requests = this.requests.filter(t => t > oneHourAgo)
// Check rate limit
if (this.requests.length >= this.requestsPerHour) {
const oldestRequest = this.requests[0]
const waitTime = oldestRequest + 3600000 - Date.now()
throw new Error(`Rate limit exceeded. Wait ${waitTime}ms`)
}
// Make request
this.requests.push(Date.now())
return fetch(url, {
...options,
headers: {
...options.headers,
'Authorization': `Bearer ${this.apiKey}`
}
})
}
}
4. Cache API Responses
const cache = new Map()
async function getCachedReservoirs(siteId) {
const cacheKey = `reservoirs-${siteId}`
// Check cache
if (cache.has(cacheKey)) {
const { data, timestamp } = cache.get(cacheKey)
// Cache valid for 5 minutes
if (Date.now() - timestamp < 300000) {
return data
}
}
// Fetch fresh data
const response = await fetch(`/api/admin/reservoirs?site_id=${siteId}`)
const data = await response.json()
// Update cache
cache.set(cacheKey, {
data,
timestamp: Date.now()
})
return data
}
5. Validate Input
import { z } from 'zod'
const ReservoirItemSchema = z.object({
title: z.string().min(1).max(255),
content: z.string().optional(),
url: z.string().url().optional(),
tags: z.array(z.string()).max(10),
source_type: z.string().optional()
})
// Validate before sending
const data = {
title: userInput.title,
content: userInput.content,
url: userInput.url,
tags: userInput.tags
}
try {
const validated = ReservoirItemSchema.parse(data)
await createReservoirItem(validated)
} catch (error) {
console.error('Validation error:', error.errors)
// Show user-friendly error
}
OAuth Integration (Coming Soon)
OAuth 2.0 support is coming soon for third-party applications. This will enable:
- User authorization without sharing API keys
- Scoped permissions (read-only, write-only, etc.)
- Token refresh and expiration
- Revocation by users
OAuth flow will look like:
1. Redirect user to:
https://blackopscenter.com/oauth/authorize?
client_id=YOUR_CLIENT_ID&
redirect_uri=https://yourapp.com/callback&
scope=reservoirs:write,posts:read
2. User approves access
3. BlackOps Center redirects back:
https://yourapp.com/callback?code=AUTHORIZATION_CODE
4. Exchange code for access token:
POST https://blackopscenter.com/oauth/token
{
"code": "AUTHORIZATION_CODE",
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET",
"grant_type": "authorization_code"
}
5. Use access token for API requests:
Authorization: Bearer ACCESS_TOKEN
Integration Gallery
Community-built integrations (coming soon):
- Notion Sync: Bi-directional sync between Notion and reservoirs
- Airtable Bridge: Use Airtable as content source
- Make (Integromat): Visual workflow automation
- n8n: Self-hosted workflow automation
- Obsidian Plugin: Save notes to reservoirs
- Readwise: Import highlights to reservoirs
Getting Help
- API Reference: Full endpoint documentation
- Webhooks Guide: Webhook configuration
- GitHub Examples: Sample integrations
- Discord Community: Ask questions, share integrations
The best tools are the ones that play well with others. Build something cool? Share it with the community.