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

  1. Log in to BlackOps Center
  2. Go to Settings → Developer → API Keys
  3. Click "Create API Key"
  4. Name it (e.g., "Mobile App", "Zapier Integration")
  5. Copy the key immediately (only shown once)
  6. 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

The best tools are the ones that play well with others. Build something cool? Share it with the community.

Want this page as machine-readable markdown? GET /docs/developer/integrations.md