Developers
API & Claude (MCP)
Create, schedule, and generate posts programmatically with a workspace API key — over plain HTTP or from Claude via the MCP server.
JackOfAllPosts has a small HTTP API so you can create, schedule, and generate posts from your own scripts, automation tools (n8n, Zapier, cron), or an LLM like Claude. It's the same publishing pipeline the app uses, so posts get retries, per-platform status, and first-comment support automatically.
Get an API key
- 1Open the API screen (the “API” item in the sidebar, or the workspace menu → API keys).
- 2Name the key, pick a scope (see below), and click Create key.
- 3Copy the key immediately — it's shown only once.
- 4Your base URL is shown on the same screen (it ends in .convex.site).
Workspace keys vs. account keys
A workspace key is locked to the one workspace it was made in — requests don't need to say which workspace. An account key reaches every workspace you can access with a single key; each request names the workspace to act on via a workspaceId field (list them with GET /v1/workspaces). Use an account key when you want one assistant that can post to all your brands; use a workspace key to hand out narrower access.
Endpoints
Authenticate every request with the header Authorization: Bearer YOUR_API_KEY. All bodies and responses are JSON.
- 1GET /v1/workspaces — the workspaces this key can act on, each with content health: scheduled (upcoming posts), goal, and needsContent (true when it's running low). Use it to see which brands need posts.
- 2GET /v1 lists every endpoint the API has, and any unknown /v1 path returns that list instead of a bare 404 — so a caller never has to guess. GET /v1/me returns it too, alongside the key's scope.
- 3Every error response carries a machine-readable `code` and a `retryable` flag (plus `retryAfterSeconds` where it applies), so an automated caller can decide what to do without parsing the message. Retry rate_limit / upstream / internal / budget_exhausted with backoff; never retry auth, validation or policy failures unchanged. GET /v1 lists the full code catalogue.
- 4GET /v1/posts, /v1/media and /v1/log are paginated: pass ?limit= and ?cursor=, and read `nextCursor` from the response (null means that was the last page).
- 5POST /v1/posts/{id}/dismiss gives up on failed targets and clears their alerts — the counterpart to retry, for failures that will never succeed. Body {targetId?}; omit it to dismiss all. The post is deleted if nothing publishable remains.
- 6GET /v1/overview — every brand at once (totals + per-brand rows); account keys only. POST /v1/workspaces {name} — create a new workspace (brand); account keys only. PATCH /v1/workspaces/{id} {name?, contentGoal?, autonomy?, autopilot?, autopilotImages?, timezone?} renames, sets the weekly content goal, or turns autopilot on. POST /v1/autopilot/run tops a brand's queue up immediately.
- 7Brand look & feel: visualStyle, visualAvoid, and colors[] on the brand profile steer AI image generation, so a brand's images stop looking like generic stock. draft brand profile fills them in too.
- 8GET /v1/brand — a workspace's brand voice profile. PATCH /v1/brand {…fields} updates it (only provided fields change; bannedWords are hard-enforced at publish).
- 9POST /v1/brand/draft — AI-draft a full brand identity from {description, examples, websiteUrl, usePosts}. Returns the profile; add apply:true to also save it.
- 10GET /v1/topics — a brand's content topics; POST /v1/topics {category, prompt} adds one; PATCH /v1/topics/{id} edits one; DELETE /v1/topics/{id} removes it. Idea generation samples across enabled topics for on-brand variety.
- 11POST /v1/posts — create a post. Body: text, platforms (e.g. ["bluesky","x"]) or accountIds, optional scheduleAt (ISO date; omit to post now), imageUrl, firstComment, variants ([{platform, text}]), workspaceId (for account keys), and draft (true = save as a draft for review instead of scheduling).
- 12PATCH /v1/posts/{id} — edit a DRAFT (attach imageUrl/imageStorageId, change text/platforms/variants/firstComment/imageAlt); imageUrl:null removes the image. Add scheduleAt (or publishNow:true) to APPROVE the draft and put it live in the same call. On an already-scheduled post, PATCH accepts text (edit copy) and scheduleAt (move it).
- 13POST /v1/posts/{id}/retry — re-queue every failed platform target on a post. Published/in-flight targets are untouched, so it can't double-post.
- 14DELETE /v1/posts/{id} — delete a draft or scheduled post.
- 15POST /v1/posts accepts an Idempotency-Key header (or idempotencyKey in the body). Retrying a create with the same key returns the ORIGINAL post instead of scheduling a duplicate — use one per intended post when automating.
- 16GET /v1/me — what this key can do: scope (workspace or account), reachable workspaces, and each brand's autonomy policy.
- 17POST /v1/validate — DRY RUN. Given text/platforms/hasImage, returns what would publish per platform, why anything would be skipped, character counts vs limits, banned-word hits, and whether the brand forces drafts. Writes nothing.
- 18GET /v1/posts/{id} — one post with full per-platform state (status, live URL, publish time, error, and whether an attempt stalled with an unknown outcome). The way to CONFIRM a specific post went live.
- 19GET /v1/approvals — drafts awaiting a human decision across every workspace the key can reach.
- 20GET/POST /v1/memory and DELETE /v1/memory/{key} — a per-brand scratchpad the assistant reads and writes, so a later session knows what an earlier one staged or decided. GET /v1/log shows what the API has already done in a brand.
- 21Autonomy per brand: PATCH /v1/workspaces/{id} {autonomy} — 'auto' lets the API schedule and publish directly; 'review' means the API can only stage drafts there and a person approves them in the app.
- 22GET /v1/budget — AI credits used vs. remaining this month against the plan covering the brand (the allowance is shared across every brand on the same bill); captionsRemaining/imagesRemaining are what stops the next call. The `limits` block reports the plan's brands, connected accounts, seats, storage and per-file ceiling, with "unlimited" where a limit is unbounded. Check before a big batch.
- 23GET /v1/analytics — engagement totals, per-platform breakdown, top posts, and bestTimes (ranked weekday+hour suggestions). Add &timezone=America/New_York for local buckets. Engagement covers X and Bluesky today.
- 24GET /v1/notifications — publish failures and 'brand going quiet' alerts (&unreadOnly=true); PATCH /v1/notifications/{id} {read:true} marks one handled.
- 25GET/POST /v1/slots and DELETE /v1/slots/{id} — the weekly posting schedule (dayOfWeek 0–6, minutes since local midnight).
- 26GET/POST /v1/recurring, PATCH /v1/recurring/{id} {active}, DELETE /v1/recurring/{id} — evergreen templates that keep a brand posting on a cadence. Creating one takes cadence + minutes + an IANA timezone; the first run is computed server-side.
- 27GET /v1/posts?limit=20 — recent posts with per-platform status and engagement. Add &workspaceId=… for an account key.
- 28GET /v1/accounts — the connected social accounts you can post to. Add ?workspaceId=… for an account key.
- 29POST /v1/generate — AI content. {"type":"caption","prompt":"…"} returns a brand-voice caption; {"type":"ideas","count":6} returns post ideas; {"type":"image","prompt":"…","quality":"text","aspect":"story"} returns {storageId, url, alt}. quality (or the alias model) is one of standard | fast | high | text | recraft | ideogram | gpt — use text for readable headlines in the image; an unknown value returns 400 with the allowed list. Include workspaceId for an account key.
- 30GET /v1/media — the workspace's media library (uploaded + AI-generated), each with a storageId to attach. Add ?workspaceId=… for an account key.
- 31Carousels: pass imageStorageIds[] (in display order, up to 10) on POST /v1/posts or PATCH /v1/posts/{id}. Instagram renders 2+ as a carousel, Bluesky and X take up to 4, and Facebook/LinkedIn/Pinterest use the first image.
- 32GET /v1/accounts/{id}/targets lists the Pinterest boards or LinkedIn Pages an account can publish to; PATCH /v1/accounts/{id} {targetId} sets the one it uses (this is what clears needsBoard/needsPage). DELETE /v1/accounts/{id} disconnects an account — one-way, since reconnecting needs a browser. DELETE /v1/media/{storageId} removes a library image, keeping the file if a post still uses it.
- 33Attaching images: pass imageStorageId (e.g. from generate image) — or a public imageUrl — plus optional imageAlt on POST /v1/posts and PATCH /v1/posts/{id}. imageStorageId wins over imageUrl. Send imageStorageId OR imageStorageIds, never both — when imageStorageIds is present, imageStorageId is ignored.
Posts scheduled via the API respect each platform's rules — Instagram/Pinterest need an image, LinkedIn needs a Company Page, Pinterest needs a board. Accounts that aren't ready are skipped, and a caption that's too long for a selected platform is rejected with a clear error.
Use it from Claude (MCP)
The repo ships an MCP server under /mcp that wraps these endpoints as tools (list/create/update_workspace, get/update/draft_brand_profile, list/add/update/delete_topic, list_accounts, list_posts, create/update/delete_post, generate_caption, generate_ideas). Point Claude Desktop or Claude Code at it with your base URL and API key, then just ask Claude to set up a brand, draft its voice, and schedule posts. With an account key, tell Claude which brand and it'll resolve the workspace via list_workspaces.
- 1In the repo: cd mcp && npm install.
- 2Claude Code: claude mcp add jackofallposts -e JOAP_BASE_URL=https://YOUR.convex.site -e JOAP_API_KEY=joap_live_… -- node /abs/path/to/mcp/index.mjs
- 3Claude Desktop: add a matching entry under mcpServers in claude_desktop_config.json (see mcp/README.md), then restart Claude.
- 4Ask Claude: “draft a launch post in our brand voice and schedule it to Bluesky and X for Tuesday 9am.”
Treat API keys like passwords — anyone with a key can post as that workspace. Revoke a key any time from the API keys screen; clients using it stop working immediately.