Use the API (and MCP)

Everything the dashboard does at the click of a button — atomize a page, generate posts, publish them — can be driven from your own pipeline instead. The API is the same product through a different door: every endpoint calls the exact same code the dashboard buttons do, so a post created over the API is indistinguishable from one created in the app.

Before you start

  1. Buy API credits in Settings → Billing (API credits card). Your first purchase also unlocks creating API keys.
  2. Create a key in Settings → API. The key is shown once — copy it immediately. Only a hash is stored; if you lose it, revoke it and mint a new one.
  3. Keys act as the whole workspace, so only workspace admins can create or revoke them. Every member can see which keys exist and when each was last used.

Authentication

Send the key on every request:

Authorization: Bearer psp_live_...

One 401 shape covers every auth failure — missing, malformed, unknown, and revoked keys are deliberately indistinguishable.

Use the www. host in every request. The bare apex domain answers with a 308 redirect, and many HTTP clients won't re-send a POST through a redirect — the request looks like it silently did nothing.

What calls cost

Reads are free. The expensive calls draw on your prepaid API credit balance (Settings → Billing):

Call Cost
POST /api/v1/generate $0.15 per post created
POST /api/v1/atomize $0.40 per call
POST /api/v1/posts/{id}/publish $0.05 per call

Generate is billed per post, not per request: one call drafts a post for every connected channel of the page's domain, so a call that creates posts for 4 channels debits $0.60. The balance is checked before any work runs — an out-of-credits call is refused with 402 payment_required and costs nothing. A call is only debited when it succeeds, and a generate that created nothing (every channel already covered) is free. Publishing to X (Twitter) additionally debits your X credit wallet, exactly as it does from the dashboard — two balances, two different costs.

Note that generate also spends your plan's monthly distribution credits (the same pool the dashboard's generate button uses). The API response separates the two: out_of_plan_credits means upgrade your plan, 402 means buy API credits.

Rate limits

Every key gets 60 requests per minute, reads included. Over the limit you get 429 rate_limited with a Retry-After header saying how many seconds to wait.

Errors

Every error has the same shape, with a stable machine-readable code your pipeline can branch on:

{ "error": { "code": "payment_required", "message": "This call costs $0.10 and your API credit balance is $0.00. ..." } }

Codes: unauthorized (401), forbidden (403), not_found (404), invalid_request (400), payment_required (402), rate_limited (429), server_error (500).

Endpoints

GET /api/v1/posts — list the workspace's posts. Free. Query filters: domain_id, platform, status, limit (default 50, max 200).

Each post includes image_url and video_url — the media attached by generation. This matters for Pinterest, which cannot publish a text-only pin: a Pinterest post with a null image_url will not go out, so check that field before publishing one.

curl -H "Authorization: Bearer psp_live_..." \
  "https://www.parasiteseoposter.com/api/v1/posts?status=generated&limit=10"

POST /api/v1/atomize — fetch a URL, extract content atoms, store the page. The URL's domain must already exist in the workspace (add it in the dashboard first). The domain is matched on host, so www. and apex forms both resolve; pass domain_id explicitly to skip matching entirely.

curl -X POST -H "Authorization: Bearer psp_live_..." -H "Content-Type: application/json" \
  -d '{"url": "https://yoursite.com/some-page"}' \
  "https://www.parasiteseoposter.com/api/v1/atomize"

Returns page_id — hold onto it for the next step.

Images

Posts need pictures — Pinterest cannot publish a post without one, and LinkedIn posts with an image perform better.

Most of the time this is automatic. Atomizing captures the page's own share image (og:image / twitter:image), re-hosts it, and attaches it to the page — generation then carries it onto every post. The atomize response tells you whether that worked: image_attached: true means you're done. Only when it's false (the page has no share image) do you need to attach one yourself.

Images attach to the page, and generation carries whatever the page has onto its posts, so the order is: atomize → attach an image, if needed → generate.

GET /api/v1/images/search?q=… — search Pexels and Unsplash, the same library the dashboard's picker uses. Free.

curl -H "Authorization: Bearer psp_live_..." \
  "https://www.parasiteseoposter.com/api/v1/images/search?q=coffee%20roasting"

POST /api/v1/pages/{page_id}/images — attach one. Free. Two accepted shapes:

# Your own image
curl -X POST -H "Authorization: Bearer psp_live_..." -H "Content-Type: application/json" \
  -d '{"image_url": "https://yoursite.com/hero.jpg"}' \
  "https://www.parasiteseoposter.com/api/v1/pages/PAGE_ID/images"

# A stock result — echo the object back from the search response
curl -X POST -H "Authorization: Bearer psp_live_..." -H "Content-Type: application/json" \
  -d '{"image": {"source":"unsplash","source_url":"https://images.unsplash.com/...","attribution_text":"Photo by … on Unsplash"}}' \
  "https://www.parasiteseoposter.com/api/v1/pages/PAGE_ID/images"

Either way the file is downloaded, stripped of EXIF, re-encoded and re-hosted on our storage, so a post never depends on a URL we don't control. GET the same path to list what a page already has. Stock photos carry attribution_text — both providers require visible credit where the image is shown.

POST /api/v1/generate — draft posts for an atomized page, one per connected channel of the page's domain. Honors your approval setting: with approval required, posts land at needs_review; without it, they're auto-scheduled.

curl -X POST -H "Authorization: Bearer psp_live_..." -H "Content-Type: application/json" \
  -d '{"page_id": "..."}' \
  "https://www.parasiteseoposter.com/api/v1/generate"

Returns created, skipped (channels that already had a live post for this page), failed, and out_of_plan_credits.

POST /api/v1/posts/{id}/publish — publish a post now, or schedule it:

curl -X POST -H "Authorization: Bearer psp_live_..." -H "Content-Type: application/json" \
  -d '{"scheduled_at": "2026-09-01T12:00:00Z"}' \
  "https://www.parasiteseoposter.com/api/v1/posts/POST_ID/publish"

An empty body (or {}) publishes at the next available slot. Posts written for hosts without a publishing API (Substack, Google Sites, …) are refused here — those go out through the dashboard's hand-off flow.

MCP

The API is also an MCP server, so Claude and other MCP clients can drive it conversationally. Point a Streamable HTTP client at:

https://www.parasiteseoposter.com/api/v1/mcp

with the same Authorization: Bearer psp_live_... header. For Claude Code:

claude mcp add --transport http parasiteseoposter https://www.parasiteseoposter.com/api/v1/mcp \
  --header "Authorization: Bearer psp_live_..."

The tools — list_posts, atomize_page, search_images, attach_image, generate_posts, publish_post — are thin wrappers over the endpoints above, with the same billing, limits, and errors.

Revoking a key

Revoke in Settings → API. Revocation is immediate — anything still using the key starts getting 401s on its next request. Revoked keys stay listed (with when they were revoked) so the audit trail survives.

Monitoring usage per key

Every metered call records which key spent the credits, so Settings → API shows a Spend (30d) column per key — dollars and call count, with all-time totals on hover. Give each pipeline its own key and you can see which one is burning the balance, then revoke just that one.