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 caption2. 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_hereGet 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
}| Field | Type | Required | Description |
|---|---|---|---|
| type | string | ✓ | blog · article · landing · marketing · email · caption |
| topic | string | ✓ | Topic or title (3–500 chars) |
| tier | string | — | basic · upgrade · pro. Default: auto (blog→pro, marketing→upgrade, caption→basic) |
| voice_mode | string | — | A (personal story) · B (principles) · C (content analysis). Default: A |
| voice_id | uuid | — | Custom voice profile ID (UUID). Omit to use the default Sunny voice. |
| brief | string | — | Additional context: audience, goals, CTA (max 5000 chars) |
| webhook | string (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:
| Tool | Purpose |
|---|---|
| write_content | Generate content end-to-end; polls until done (3-min cap), then returns final draft + quality score. |
| list_voices | List your voice profiles (metadata only). |
| get_content_status | Check 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 usageOr 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
| Tier | Limit | Custom voices | Content types |
|---|---|---|---|
| Free | 10/month | 0 (default voice) | caption · marketing |
| Starter | 100/month | 1 | all |
| Pro | 500/month | 5 | all + pro tier |
| Enterprise | Custom | Unlimited | all |
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.