How to build a content agent
This walkthrough builds a TypeScript agent with three tools: list accounts, generate captions, and schedule a post. It runs on a Socifyr API key with guardrails, so the agent can propose posts but a human approves them before anything publishes. The model call uses the Anthropic SDK; the Socifyr calls use @socifyr/sdk. You can swap in any model that supports tool use.
If you only want an existing assistant to use Socifyr, skip this and use MCP. Build your own when you need custom logic, a schedule, or your own model provider.
What do I need first?
- A Socifyr account with at least one connected account and a plan with scheduling.
- A guarded key for the agent. In the dashboard open API Keys, create a key, set a daily post limit (for example 5) and turn on require approval. See Approvals and guardrails.
- An AI key added to the workspace if you want
generate_caption(see AI captions). - Node.js 18 or newer.
npm install @socifyr/sdk @anthropic-ai/sdk
export SOCIFYR_API_KEY=sk_agent_key
export SOCIFYR_WORKSPACE_ID=69fbbf978e770da397a59a5a
export ANTHROPIC_API_KEY=...
export ANTHROPIC_MODEL=<a model id from your Anthropic account>How do I define the tools?
Each tool wraps one SDK call. The SDK calls below are all allowed for a guarded key: listing connections, generating captions and creating a scheduled post.
import Anthropic from '@anthropic-ai/sdk'
import { Socifyr } from '@socifyr/sdk'
const socifyr = new Socifyr({
apiKey: process.env.SOCIFYR_API_KEY!,
workspaceId: process.env.SOCIFYR_WORKSPACE_ID!,
})
const anthropic = new Anthropic()
const tools: Anthropic.Tool[] = [
{
name: 'list_connections',
description: 'List connected social accounts and their slugs.',
input_schema: { type: 'object', properties: {} },
},
{
name: 'generate_caption',
description: 'Generate one caption per platform from a short brief.',
input_schema: {
type: 'object',
properties: {
description: { type: 'string' },
platforms: { type: 'array', items: { type: 'string' } },
},
required: ['description', 'platforms'],
},
},
{
name: 'schedule_post',
description: 'Schedule a text post. scheduledAt is ISO 8601 in the future.',
input_schema: {
type: 'object',
properties: {
text: { type: 'string' },
connections: { type: 'array', items: { type: 'string' } },
scheduledAt: { type: 'string' },
},
required: ['text', 'connections', 'scheduledAt'],
},
},
]
async function runTool(name: string, input: any): Promise<unknown> {
switch (name) {
case 'list_connections': {
const all = await socifyr.workspaces.listConnections(process.env.SOCIFYR_WORKSPACE_ID!)
return all.map((c) => ({ slug: c.slug, platform: c.platform, status: c.status }))
}
case 'generate_caption':
return socifyr.ai.generateContent({
uploadType: 'text',
platforms: input.platforms,
description: input.description,
tone: 'professional',
})
case 'schedule_post':
return socifyr.uploads.text({
connections: input.connections,
text: input.text,
scheduledAt: input.scheduledAt,
})
default:
throw new Error(`Unknown tool ${name}`)
}
}How do I run the agent loop?
Send the user's request, run any tool the model asks for, return the results, and repeat until the model stops asking.
async function runAgent(request: string) {
const messages: Anthropic.MessageParam[] = [{ role: 'user', content: request }]
for (let turn = 0; turn < 8; turn++) {
const res = await anthropic.messages.create({
model: process.env.ANTHROPIC_MODEL!,
max_tokens: 1024,
system:
'You schedule social posts for a team. Always call list_connections first. ' +
'Use only slugs it returns. Never invent times: use the dates the user gives you.',
tools,
messages,
})
messages.push({ role: 'assistant', content: res.content })
if (res.stop_reason !== 'tool_use') {
return res.content.filter((b) => b.type === 'text').map((b: any) => b.text).join('\n')
}
const results: Anthropic.ToolResultBlockParam[] = []
for (const block of res.content) {
if (block.type !== 'tool_use') continue
try {
const out = await runTool(block.name, block.input)
results.push({ type: 'tool_result', tool_use_id: block.id, content: JSON.stringify(out) })
} catch (err) {
results.push({
type: 'tool_result',
tool_use_id: block.id,
content: err instanceof Error ? err.message : String(err),
is_error: true,
})
}
}
messages.push({ role: 'user', content: results })
}
throw new Error('Agent did not finish in 8 turns')
}
console.log(await runAgent(
'Write a LinkedIn post announcing our webinar and schedule it for 2026-12-08T09:00:00Z.',
))Errors go back to the model as tool results with is_error, so it can correct a bad slug or time on its own.
What happens when the agent schedules a post?
Because the key requires approval, socifyr.uploads.text() returns an acknowledgement with status: "pending_approval":
{
"success": true,
"uploadId": "6a05032a84584482ace13d5f",
"status": "pending_approval",
"message": "This API key requires approval — the post is pending review before it publishes."
}Nothing is queued yet. A teammate opens Content in the dashboard, checks the post, and clicks Approve. Approving publishes it, or schedules it for its scheduledAt. Reject sends it back to draft. See Drafts and approvals.
What are the safety checks?
- Guarded key. The agent cannot approve, change its limits, mint keys, refresh accounts or call webhooks. The API enforces this, not your prompt.
- Daily limit. Even a loop that runs away stops at the daily cap with a 403.
- Turn limit. The loop above stops after 8 turns.
- Idempotency. Add an
idempotencyKeytouploads.text()if your scheduler may rerun the job. - Log tool calls. Log each
block.nameandblock.input, never the API keys.
How do I run it on a schedule?
Run the script from cron, GitHub Actions or any job runner. Keep one guarded key per agent so you can read its activity and rotate it without touching other tools.
FAQ
Can I use MCP instead of wrapping the SDK?
Yes. Connect the hosted server and let the client handle tools. This walkthrough is for when you want your own loop.
Why use the SDK tools instead of the REST API?
Same endpoints, less code. A Python agent can use socifyr.Socifyr the same way, with uploads.text() and ai.generate_caption().
Why did my agent get a 403?
Read the message. It names the guardrail or plan limit. See the error reference.
Next steps
- Example agent prompts
- Approvals and guardrails
- Webhooks to react when a post publishes
- TypeScript SDK reference