# Automate10x — Agentic Publishing API (full reference) Base URL: https://automate10x.com Auth: every request needs Authorization: Bearer sk_ - Get a key at https://automate10x.com/settings/api. Never trim a trailing '=' from the key. Content-Type: application/json on writes. Idempotency: send Idempotency-Key: on POST to make retries safe; the first response is replayed for 24h (header Idempotency-Replayed: true). Reusing a key with a different body is a 409 conflict. Request id: every response carries X-Request-Id. Billable calls also carry X-Credits-Cost and X-Credits-Remaining. ## Error envelope (identical everywhere) { "error": { "type", "code", "message", "field"?, "docs", "requestId" } } type ∈ auth_error | validation_error | not_found | rate_limited | insufficient_credits | platform_error | conflict | internal_error Agents branch on "code"; humans read "message"; both get a "docs" URL. ## 1. Verify the key GET /api/v2/users/me -> { userId, email, activeBrandId, plan{ id, displayName, creditsPerMonth, maxConnections, maxBrands }, creditsRemaining, scopes[], keyId, keyLastFour } ## 2. Capabilities (self-configure) GET /api/v2/capabilities -> { platforms: { : { charLimit, media, requires[], options[] } }, accounts: [ { id, platform, displayName, status, subaccounts:[{id,name,kind}] } ] } Per-platform rules (live): - linkedin: charLimit=3000; media={image:9 video:1}; requires=[]; options=[pageId, carousel, truncate] - x: charLimit=280; media={image:4 video:1}; requires=[]; options=[truncate] - instagram: charLimit=2200; media={image:10 video:1 required}; requires=[]; options=[mediaType, altText, coverImageUrl, shareToFeed, collaborators, truncate] - facebook: charLimit=63206; media={image:10 video:1}; requires=[pageId]; options=[mediaType, link, truncate] - tiktok: charLimit=2200; media={video:1 required}; requires=[privacyLevel]; options=[title, disabledComments, disabledDuet, disabledStitch, isAiGenerated, truncate] - youtube: charLimit=5000; media={video:1 required}; requires=[title, privacyStatus]; options=[type, playlistIds, thumbnailUrl, isMadeForKids, truncate] - threads: charLimit=500; media={image:10 video:1}; requires=[]; options=[replyControl, truncate] - pinterest: charLimit=500; media={image:1 video:1 required}; requires=[boardId]; options=[title, altText, link, truncate] - bluesky: charLimit=300; media={image:4 video:1}; requires=[]; options=[truncate] Notes: - "requires" options MUST be present in a target's options or the publish/dryRun fails with code missing_required_option. - Unknown options for a platform are rejected (code unknown_option), never silently dropped. - "truncate": true in a target's options allows over-limit text to be truncated instead of rejected. By default over-limit text is a hard error (text_too_long). - pageId (LinkedIn/Facebook) and boardId (Pinterest) select the channel/board. ## 3. Publish (one call → many destinations) POST /api/v2/posts Body (recommended): { "text": "default text used by every target unless overridden", "media": ["https://cdn.example.com/a.png"], "targets": [ { "accountId": "" }, { "accountId": "", "options": { "pageId": "" } }, { "accountId": "", "media": ["https://.../v.mp4"], "options": { "privacyLevel": "PUBLIC_TO_EVERYONE", "title": "Launch" } } ], "schedule": { "mode": "now" } } schedule.mode: - { "mode": "now" } - { "mode": "at", "time": "2026-06-12T15:00:00Z" } (ISO 8601 UTC) - { "mode": "next_free_slot", "platform": "x" } Response (200): { "jobId", "status": "queued", "targets": [ { id, accountId, platform, status } ], "schedule", "poll": "/api/v2/posts/", "credits": { cost, remaining } } Validate first (queues nothing): POST /api/v2/posts?dryRun=true -> { dryRun: true, valid, targets: [ { accountId, platform, valid, errors: [ { code, message, field } ] } ] } Backward compatible legacy body (returns the single row at 201): { "accountId": "", "body": "text", "mediaUrls": ["..."], "scheduledAt": "now" } ## 4. Poll the job GET /api/v2/posts/ -> { jobId, status, targets: [ { id, accountId, platform, status, url, externalId, error?: { code, message } } ] } status ∈ queued | running | completed | partially_failed | failed Partial failure is first-class: one platform can fail while the rest succeed. ## 5. Research feedstock POST /api/v2/sources { "brandId": "", "type": "article", "url": "https://example.com", "customInstructions": "Pull the 3 key stats." } type ∈ article | youtube | tiktok | perplexity | audio | pdf | text - article: any web page/URL (aliases: url, link). pass "url". - perplexity: pass "query" for live web search. - text: pass "content" inline — resolves immediately (status=ready). - youtube/tiktok: transcript extraction. audio: transcription. pdf: text extraction. Unknown types (e.g. rss) are rejected with 400 invalid_type. ## 6. Generate POST /api/v2/generate { prompt, sources?: string[], platform?, n?, brandVoice? } -> post variants with token counts. ## 7. Lists (cursor pagination is opt-in: add ?limit=50 or ?cursor=...) - GET /api/v2/accounts - GET /api/v2/scheduled-posts filters: status, platform, jobId, from, to - GET /api/v2/published-posts filters: platform, from, to - GET /api/v2/brands Paginated responses: { data: [...], nextCursor, hasMore }. Without limit/cursor the response is a bare array (backward compatible). ## OpenAPI + MCP - https://automate10x.com/openapi.json (OpenAPI 3.1 — generate SDKs / import to Postman) - If you speak MCP, prefer the MCP server: its tools auto-poll to a settled result so creation feels synchronous.