API Reference

Base URL: https://content-apiapi-production.up.railway.app · New here? Read how it works first.

Install

Three ways to use content-api — pick one or mix them:

1. CLI (terminal)

npm install -g @content-agent/cli

content-agent auth set capi_your_key
content-agent write "AI productivity tips" --type caption

2. MCP (Claude Desktop / VS Code / Cursor)

Add to ~/.claude/claude_desktop_config.json (or .vscode/mcp.json):

{
  "mcpServers": {
    "content-api": {
      "type": "streamable-http",
      "url": "https://content-apiapi-production.up.railway.app/mcp",
      "headers": { "x-api-key": "capi_your_key" }
    }
  }
}

Restart Claude Desktop. Type write a caption about ... — the write_content tool runs.

3. REST API (curl, any HTTP client)

curl -X POST https://content-apiapi-production.up.railway.app/v1/content/generate \
  -H "x-api-key: capi_your_key" \
  -H "Content-Type: application/json" \
  -d '{"type":"caption","topic":"AI productivity tips"}'

Authentication

Pass your API key via header. Two formats accepted:

x-api-key: capi_your_key_here
# or
Authorization: Bearer capi_your_key_here

Get a key by registering at /register and clicking the email verification link. The key is shown once — save it.

Content Generation

POST /v1/content/generate

Create a content generation job. Returns immediately with a job_id. Poll GET /v1/content/:id or set a webhook.

curl -X POST https://content-apiapi-production.up.railway.app/v1/content/generate \
  -H "x-api-key: capi_..." \
  -H "Content-Type: application/json" \
  -d '{
    "type": "blog",
    "topic": "AI trong HR: cơ hội hay mối đe dọa?",
    "tier": "pro",
    "voice_mode": "A"
  }'

# Response 202
{
  "job_id": "550e8400-e29b-41d4-a716-446655440000",
  "estimated_seconds": 60
}
FieldTypeRequiredDescription
typestringblog · article · landing · marketing · email · caption
topicstringTopic or title (3–500 chars)
tierstringbasic · upgrade · pro. Default: auto (blog→pro, marketing→upgrade, caption→basic)
voice_modestringA (personal story) · B (principles) · C (content analysis). Default: A
voice_iduuidCustom voice profile ID (UUID). Omit to use the default Sunny voice.
briefstringAdditional context: audience, goals, CTA (max 5000 chars)
webhookstring (URL)Receive result via webhook instead of polling

Errors: 400 validation (e.g. voice_id not a UUID) · 403 tier restriction · 404 voice not found · 429 monthly quota · 503 queue temporarily unavailable.

GET /v1/content/:job_id

Poll job status. Poll every 3–5s until status is completed, rejected, or failed.

curl https://content-apiapi-production.up.railway.app/v1/content/JOB_ID \
  -H "x-api-key: capi_..."

# When completed:
{
  "job_id": "...",
  "status": "completed",
  "content": "...",
  "quality_score": 9,
  "voice_version": "sha1:abc123def456",
  "tier": "pro",
  "voice_mode": "A",
  "review_report": { "verdict": "APPROVE", "voice_score": 9, "issues": [], "patches": [] },
  "generated_at": "2026-05-16T16:30:00.000Z"
}

voice_version is the SHA-1 of the voice JSON at the time of generation. Snapshotting this with the job gives a stable audit trail even if the voice is later updated.

Voice Profiles

POST /v1/voices

Create a voice profile from writing samples. Requires paid plan.

curl -X POST https://content-apiapi-production.up.railway.app/v1/voices \
  -H "x-api-key: capi_..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "My Voice",
    "samples": ["Sample post 1...", "Sample post 2..."],
    "hard_rules": ["no_emoji", "no_emdash"],
    "mode_default": "A"
  }'

# Response 201
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "name": "My Voice",
  "voice_version": "abc123def456"
}

GET /v1/voices

List your voice profiles. Returns metadata only — the raw voice JSON (writing samples + hard rules) stays server-side and is never exposed via any endpoint.

curl https://content-apiapi-production.up.railway.app/v1/voices \
  -H "x-api-key: capi_..."

# Response
{
  "voices": [
    {
      "id":            "550e8400-e29b-41d4-a716-446655440000",
      "name":          "My Voice",
      "voice_version": "abc123def456",
      "created_at":    "2026-05-16T10:00:00.000Z"
    }
  ]
}

GET /v1/voices/:id

Get metadata for a single voice. Same shape as the list entries above — no voice_json field is returned.

PUT /v1/voices/:id

Update a voice profile. Auto-bumps voice_version (SHA-1 of new JSON). Existing jobs keep the version that was active when they were created.

Webhooks

Set webhook on the generate request to receive completion notifications. Skip polling.

POST <your webhook URL>
Headers:
  Content-Type: application/json
  X-Content-Signature: sha256=<hex digest>

Body:
{
  "job_id":        "550e8400-...",
  "status":        "completed",      // completed | rejected | failed
  "content":       "Final content text...",
  "quality_score": 9,
  "review_report": { "verdict": "APPROVE", "voice_score": 9, ... }
}

Verifying the signature

Each delivery includes X-Content-Signature: sha256=..., an HMAC-SHA256 of the raw request body using a shared secret. Verify in constant time before trusting the payload:

// Node.js example
import { createHmac, timingSafeEqual } from "crypto";

function verify(body, signatureHeader, secret) {
  const provided = signatureHeader.replace("sha256=", "");
  const expected = createHmac("sha256", secret).update(body).digest("hex");
  return timingSafeEqual(Buffer.from(provided), Buffer.from(expected));
}

Retry policy: 3 attempts with exponential backoff (1s · 2s · 4s). After 3 failures the content is still saved — poll GET /v1/content/:id. The signing secret is shared during onboarding for paid plans.

POST /v1/webhooks/test

Verify your endpoint is reachable before relying on it.

MCP — Claude Desktop / VS Code / Cursor

// claude_desktop_config.json
{
  "mcpServers": {
    "content-api": {
      "type": "streamable-http",
      "url": "https://content-apiapi-production.up.railway.app/mcp",
      "headers": { "x-api-key": "capi_your_key" }
    }
  }
}

Available MCP tools:

ToolPurpose
write_contentGenerate content end-to-end; polls until done (3-min cap), then returns final draft + quality score.
list_voicesList your voice profiles (metadata only).
get_content_statusCheck status of a running job by job_id.

CLI

The @content-agent/cli package installs a content-agent binary for use from any terminal.

npm install -g @content-agent/cli

# Save your API key once
content-agent auth set capi_your_key

# Write content
content-agent write "AI trong HR" --type caption
content-agent write "5 sai lầm tuyển dụng" --type article --tier pro --mode B
content-agent write "Brand story" --type blog --out story.md

# Check status of a long-form job
content-agent status JOB_ID

# Check quota
content-agent usage

Or use plain curl — no install required:

# Generate
curl -X POST https://content-apiapi-production.up.railway.app/v1/content/generate \
  -H "x-api-key: capi_your_key" \
  -H "Content-Type: application/json" \
  -d '{"type":"caption","topic":"AI productivity tips"}'

# Poll
curl https://content-apiapi-production.up.railway.app/v1/content/JOB_ID \
  -H "x-api-key: capi_your_key"

Rate limits

TierLimitCustom voicesContent types
Free10/month0 (default voice)caption · marketing
Starter100/month1all
Pro500/month5all + pro tier
EnterpriseCustomUnlimitedall

Failed and rejected jobs do not count toward your quota — you're only charged for usable output. Live count returned via X-RateLimit-Remaining header on every generation. See pricing.