Skip to main content
Reference

ClimbX API docs

A small REST API for AI agents to publish and schedule X posts and read analytics and voice data on behalf of a ClimbX account. JSON in, JSON out. v1 is intentionally minimal.

Authentication

Every request needs a key in the Authorization header. Create one in ClimbX under Settings → API. The full key is shown once at creation - store it somewhere safe. We keep only a hash and cannot show it again.

Authorization: Bearer climbx_sk_your_key

The owning account must be on an active plan or trial. A lapsed subscription returns 402 even if the key is still live. Revoke a key any time from the same screen; it stops working on the next call.

Keys come in two scopes, chosen at creation: read & write (the default - full API) or read-only (analytics, voice, learnings, and inspiration only). A read-only key calling a write endpoint - publish, schedule, reschedule, cancel, or draft a reply - gets 403 read_only_key. Use read-only keys for dashboards, monitoring, or any agent that should never post.

Base URL and limits

https://climbx.so/api/v1
  • Daily post cap: 5 posts per account per day across publish and schedule. Resets at 00:00 UTC. Over the cap returns 429.
  • No links: posts containing a URL are rejected with 400. Link posts cut reach and cost 13x to publish.
  • Content type: send Content-Type: application/json on every POST and PATCH.
  • Media: attach up to 4 images by passing image_urls (public https URLs) on publish or schedule. Video over the API is a work in progress.
  • Read rate limit: GET endpoints allow about 60 requests per minute per key. Over it returns 429 with a Retry-After header.
  • Crawling analytics: the analytics and learnings-history endpoints take a start and end ISO window (default last 30 days, max span 366 days). Every response carries generated_at and as_of (the newest metrics snapshot in the window) - poll on a schedule and dedupe on as_of to build a time series.
  • Freshness: using the API refreshes your data in the background - new posts, metrics, and learnings - the same way opening the app does, so you never have to visit the web app to keep these endpoints current. Refreshes are throttled, so the first call in a window does the work and later calls read the fresh result.

MCP server

Every endpoint below is also available as an MCP tool at https://climbx.so/mcp - the fastest way to give Claude Code, Claude Desktop, Cursor, or any MCP client full ClimbX access. No install; authenticate with the same Bearer key:

claude mcp add --transport http climbx https://climbx.so/mcp \
  --header "Authorization: Bearer climbx_sk_your_key"

Tools mirror the REST API one-to-one (publish_post, schedule_post, get_analytics, get_voice_profile, get_following_outliers, suggest_reply, ...) and enforce the same caps, rate limits, and guardrails - the MCP layer adds no separate rules. Prefer a local stdio server? The community-built climbx-cowork MCP wraps the same API.

Pair the server with the ClimbX Agent Skill - an installable guide that teaches an agent which tool to reach for, the drafting rules, and how to budget the daily cap. The MCP server executes; the skill reasons.

Publish a post now

POST/api/v1/posts

Ships a post to X immediately through the connected account. Attach up to 4 images with image_urls.

FieldTypeDescription
textstringrequiredThe post body. 1 to 10,000 chars. No URLs.
image_urlsstring[]optionalUp to 4 public https image URLs. Each is fetched and attached. Video is not supported yet.
curl -X POST https://climbx.so/api/v1/posts \
  -H "Authorization: Bearer climbx_sk_your_key" \
  -H "Content-Type: application/json" \
  -d '{"text": "shipped something today."}'
Response 200
{
  "ok": true,
  "id": "1799999999999999999",
  "url": "https://x.com/i/web/status/1799999999999999999",
  "record_id": "9f1c...",
  "posts_used_today": 1,
  "daily_cap": 5
}

List recent posts with metrics

GET/api/v1/posts

Returns the account’s recent published posts and their latest metrics snapshot.

FieldTypeDescription
limitnumberoptionalHow many to return. 1 to 100, default 30.
curl https://climbx.so/api/v1/posts?limit=10 \
  -H "Authorization: Bearer climbx_sk_your_key"
Response 200
{
  "posts": [
    {
      "id": "1799...",
      "text": "...",
      "format": "hot_take",
      "posted_at": "2026-05-25T08:30:00Z",
      "is_reply": false,
      "metrics": {
        "impressions": 14200,
        "views": 14200,
        "likes": 180,
        "replies": 24,
        "retweets": 12,
        "quote_tweets": 3
      }
    }
  ]
}

Schedule a post

POST/api/v1/schedule

Queues a post for a future time. ClimbX ships it at the scheduled minute and retries on transient failures. A past time publishes on the next tick.

FieldTypeDescription
textstringrequiredThe post body. 1 to 10,000 chars. No URLs.
scheduled_forstringrequiredISO 8601 datetime, e.g. 2026-06-01T14:00:00Z.
image_urlsstring[]optionalUp to 4 public https image URLs, attached at publish time. No video yet.
curl -X POST https://climbx.so/api/v1/schedule \
  -H "Authorization: Bearer climbx_sk_your_key" \
  -H "Content-Type: application/json" \
  -d '{"text": "morning thought.", "scheduled_for": "2026-06-01T08:00:00Z"}'
Response 200
{
  "ok": true,
  "scheduled": {
    "id": "3a2b...",
    "text": "morning thought.",
    "scheduled_for": "2026-06-01T08:00:00Z",
    "status": "pending"
  },
  "posts_used_today": 2,
  "daily_cap": 5
}

List scheduled posts

GET/api/v1/schedule

Returns upcoming posts that are still pending or mid-publish.

curl https://climbx.so/api/v1/schedule \
  -H "Authorization: Bearer climbx_sk_your_key"
Response 200
{
  "scheduled": [
    {
      "id": "3a2b...",
      "text": "morning thought.",
      "scheduled_for": "2026-06-01T08:00:00Z",
      "status": "pending"
    }
  ]
}

Reschedule a pending post

PATCH/api/v1/schedule/{id}

Moves a still-pending scheduled post to a new time. Works only while the post is pending; once ClimbX has started publishing it, it can’t be changed (returns 409).

FieldTypeDescription
scheduled_forstringrequiredNew ISO 8601 datetime.
curl -X PATCH https://climbx.so/api/v1/schedule/3a2b... \
  -H "Authorization: Bearer climbx_sk_your_key" \
  -H "Content-Type: application/json" \
  -d '{"scheduled_for": "2026-06-02T09:30:00Z"}'
Response 200
{
  "ok": true,
  "scheduled": {
    "id": "3a2b...",
    "text": "morning thought.",
    "scheduled_for": "2026-06-02T09:30:00Z",
    "status": "pending"
  }
}

Cancel a scheduled post

DELETE/api/v1/schedule/{id}

Cancels a still-pending scheduled post so it won’t publish. Cancelling does not give back a daily-cap slot - the post counted when you created it.

curl -X DELETE https://climbx.so/api/v1/schedule/3a2b... \
  -H "Authorization: Bearer climbx_sk_your_key"
Response 200
{
  "ok": true,
  "cancelled": { "id": "3a2b...", "status": "cancelled" }
}

Performance summary

GET/api/v1/analytics

Headline KPIs plus a per-format breakdown over a window. Replies are excluded.

FieldTypeDescription
daysnumberoptionalLookback window. 1 to 90, default 30.
curl https://climbx.so/api/v1/analytics?days=30 \
  -H "Authorization: Bearer climbx_sk_your_key"
Response 200
{
  "range_days": 30,
  "summary": {
    "posts_published": 42,
    "total_impressions": 512000,
    "avg_impressions": 12190,
    "avg_replies": 18,
    "avg_likes": 140,
    "engagement_rate_pct": 2.8
  },
  "by_format": [
    { "format": "hot_take", "posts": 12, "median_replies": 22, "median_impressions": 15000 }
  ]
}

Format performance

GET/api/v1/analytics/formats

Per-format breakdown over a window: how many posts, their share, and the median replies and impressions, with a trend (up/down/neutral) comparing each format against your typical post in the same window. Mirrors the Format performance table in the app.

FieldTypeDescription
startstringoptionalISO datetime window start. Defaults to 30 days before end.
endstringoptionalISO datetime window end. Defaults to now.

generated_at is when the response was computed; as_of is the newest metrics snapshot in the window (when the numbers last moved). Max span 366 days.

curl "https://climbx.so/api/v1/analytics/formats?start=2026-05-01T00:00:00Z&end=2026-06-01T00:00:00Z" \
  -H "Authorization: Bearer climbx_sk_your_key"
Response 200
{
  "generated_at": "2026-06-01T12:00:00Z",
  "as_of": "2026-05-31T22:10:00Z",
  "window": { "start": "2026-05-01T00:00:00Z", "end": "2026-06-01T00:00:00Z" },
  "posts_analyzed": 42,
  "median_impressions": 12000,
  "rows": [
    {
      "format": "hot_take",
      "posts": 12,
      "share": 29,
      "replies": { "value": 22, "trend": "up" },
      "impressions": { "value": 15000, "trend": "up" }
    }
  ]
}

Niche performance

GET/api/v1/analytics/niches

Same shape as Format performance, bucketed by niche instead of format. Posts with no niche label come back under "__unlabeled__".

FieldTypeDescription
startstringoptionalISO datetime window start. Defaults to 30 days before end.
endstringoptionalISO datetime window end. Defaults to now.
curl "https://climbx.so/api/v1/analytics/niches?start=2026-05-01T00:00:00Z&end=2026-06-01T00:00:00Z" \
  -H "Authorization: Bearer climbx_sk_your_key"
Response 200
{
  "generated_at": "2026-06-01T12:00:00Z",
  "as_of": "2026-05-31T22:10:00Z",
  "window": { "start": "2026-05-01T00:00:00Z", "end": "2026-06-01T00:00:00Z" },
  "posts_analyzed": 42,
  "median_impressions": 12000,
  "rows": [
    {
      "niche": "saas",
      "posts": 18,
      "share": 43,
      "replies": { "value": 20, "trend": "neutral" },
      "impressions": { "value": 13500, "trend": "up" }
    }
  ]
}

Voice profile

GET/api/v1/voice

The account’s voice persona, evidence-backed learnings, cadence targets, and posting schedule. Use it to draft in the owner’s voice and time posts well.

curl https://climbx.so/api/v1/voice \
  -H "Authorization: Bearer climbx_sk_your_key"
Response 200
{
  "voice": {
    "persona": "lowercase, short lines, founder-to-founder...",
    "mode": "adaptive",
    "learnings": [
      { "text": "open with a contradiction", "polarity": "positive", "evidence": "..." }
    ],
    "cadence": { "phase": "grow", "posts_per_day": 2, "replies_per_day": 5 },
    "schedule": {
      "timezone": "Europe/Copenhagen",
      "active_start_hour": 8,
      "active_end_hour": 22,
      "weekly_slots": { "mon": ["09:00", "17:00"] }
    }
  }
}

Ongoing learnings

GET/api/v1/learnings

The account’s current “Ongoing learnings” - the do-more (positive) and do-less (negative) rules ClimbX derived from the owner’s own posts, each with its evidence. last_derived_at is when the set was last recomputed.

curl https://climbx.so/api/v1/learnings \
  -H "Authorization: Bearer climbx_sk_your_key"
Response 200
{
  "generated_at": "2026-06-01T12:00:00Z",
  "last_derived_at": "2026-05-30T03:00:00Z",
  "learnings": {
    "positive": [
      { "text": "open with a contradiction", "source": "derived", "evidence": "..." }
    ],
    "negative": [
      { "text": "long multi-paragraph posts underperform", "source": "derived", "evidence": "..." }
    ]
  }
}

Learnings history

GET/api/v1/learnings/history

The recorded timeline of the learnings set - one snapshot each time it was re-derived, within the window. Use it to chart how the rules evolved. History accrues going forward from when the feature shipped; there is no retroactive backfill.

FieldTypeDescription
startstringoptionalISO datetime window start. Defaults to 30 days before end.
endstringoptionalISO datetime window end. Defaults to now.
curl "https://climbx.so/api/v1/learnings/history?start=2026-05-01T00:00:00Z&end=2026-06-01T00:00:00Z" \
  -H "Authorization: Bearer climbx_sk_your_key"
Response 200
{
  "generated_at": "2026-06-01T12:00:00Z",
  "window": { "start": "2026-05-01T00:00:00Z", "end": "2026-06-01T00:00:00Z" },
  "snapshots": [
    {
      "captured_at": "2026-05-30T03:00:00Z",
      "learnings": { "positive": [ ... ], "negative": [ ... ] }
    }
  ]
}

Filter options

GET/api/v1/inspiration/options

A self-describing map of the filters both inspiration feeds accept - every enumerated value with its label and default - plus your tracked_handles. Fetch it once to populate dropdowns or to teach an agent what to send; the enum values map directly onto a tool schema. Takes no params.

curl https://climbx.so/api/v1/inspiration/options \
  -H "Authorization: Bearer climbx_sk_your_key"
Response 200
{
  "generated_at": "2026-06-01T12:00:00Z",
  "feeds": {
    "following": {
      "path": "/api/v1/inspiration/following",
      "filters": {
        "handles": { "kind": "csv", "options_from": "tracked_handles" },
        "limit": { "kind": "range", "default": 30, "min": 1, "max": 100 }
      }
    },
    "surprise": {
      "path": "/api/v1/inspiration/surprise",
      "filters": {
        "min_multiplier": {
          "kind": "enum", "default": 1.5,
          "options": [
            { "value": 1.5, "label": "1.5x+" },
            { "value": 3, "label": "3x+" }
          ]
        },
        "format": {
          "kind": "enum", "default": null,
          "options": [ { "value": "hot_take", "label": "Hot take", "description": "..." } ]
        },
        "recency": { "kind": "enum", "default": "all", "options": [ ... ] },
        "image": { "kind": "enum", "default": "all", "options": [ ... ] }
      }
    }
  },
  "tracked_handles": [ { "handle": "@levelsio", "name": "Pieter Levels" } ]
}

Following outliers

GET/api/v1/inspiration/following

Outlier posts from the creators you track - the app’s Following feed. The tracked list is read from your account; pass handles to narrow to a subset of it. Every item is a real post that outperformed its author’s baseline, with a multiplier (post vs author baseline) and a deep post_url. DB-only and read-only: no scanning is triggered, so responses reflect data already scanned in the app.

FieldTypeDescription
handlesstringoptionalComma-separated handles to narrow to a subset of the creators you track. Omit for all of them.
limitnumberoptionalMax outliers to return, 1-100. Defaults to 30.
curl "https://climbx.so/api/v1/inspiration/following?limit=20" \
  -H "Authorization: Bearer climbx_sk_your_key"
Response 200
{
  "generated_at": "2026-06-01T12:00:00Z",
  "feed": "following",
  "creator_count": 4,
  "outliers": [
    {
      "handle": "@levelsio",
      "name": "Pieter Levels",
      "id": "a1b2...",
      "text": "shipped it in a weekend...",
      "format": "build_in_public",
      "niche": "saas",
      "posted_at": "2026-05-28T09:12:00Z",
      "replies": 120,
      "likes": 3400,
      "retweets": 210,
      "impressions": 180000,
      "multiplier": 4.2,
      "post_url": "https://x.com/levelsio/status/...",
      "image_urls": []
    }
  ]
}

Surprise me

GET/api/v1/inspiration/surprise

Random outliers from across the network, excluding creators you already track - the app’s “Surprise me” discovery feed. Same post shape as Following outliers. Filters snap to the app’s buckets. DB-only and read-only: no scanning is triggered.

FieldTypeDescription
min_multipliernumberoptionalFloor on the outlier multiplier; snaps to 1.5, 2, 3, or 7.
min_impressionsnumberoptionalFloor on impressions; snaps to 0, 10000, 50000, or 100000.
formatstringoptionalRestrict to one format (e.g. hot_take); all = no filter.
recencystringoptional7d or 30d to limit to recent posts; all = no floor.
imagestringoptionalwith = posts with media, without = text-only.
limitnumberoptionalMax outliers to return, 1-100. Defaults to 30.
curl "https://climbx.so/api/v1/inspiration/surprise?min_multiplier=3&recency=30d" \
  -H "Authorization: Bearer climbx_sk_your_key"
Response 200
{
  "generated_at": "2026-06-01T12:00:00Z",
  "feed": "surprise",
  "creator_count": 6,
  "outliers": [
    {
      "handle": "@levelsio",
      "name": "Pieter Levels",
      "id": "a1b2...",
      "text": "shipped it in a weekend...",
      "format": "build_in_public",
      "niche": "saas",
      "posted_at": "2026-05-28T09:12:00Z",
      "replies": 120,
      "likes": 3400,
      "retweets": 210,
      "impressions": 180000,
      "multiplier": 4.2,
      "post_url": "https://x.com/levelsio/status/...",
      "image_urls": []
    }
  ]
}

Draft a reply

POST/api/v1/engage/suggest-reply

Given a post you are considering replying to, returns ONE suggested reply in the account owner’s voice - the same drafting core, credit pool, and guardrails as the Engage tab (this is what the ClimbX Chrome extension calls). The draft is a suggestion: the human edits and sends it on X themselves. Drafting stays locked (403 locked) until the owner has written enough replies in their own words in the app, and each draft spends one shared daily AI credit (402 when the pool is empty).

FieldTypeDescription
textstringrequiredThe post to reply to. 1 to 4000 chars.
authorHandlestringoptionalThe post author’s handle, without the @.
curl -X POST https://climbx.so/api/v1/engage/suggest-reply \
  -H "Authorization: Bearer climbx_sk_your_key" \
  -H "Content-Type: application/json" \
  -d '{"text": "shipped my first saas in a weekend, what should i build next?", "authorHandle": "levelsio"}'
Response 200
{
  "reply": "the weekend ship is the easy part, the scary question is which one you keep showing up for. what pulled you back so far?"
}

Errors

Errors return a JSON body with an error code and a human message.

StatuserrorWhen
400invalid_body / invalid_query / url_posts_not_allowed / invalid_id / invalid_windowBad payload or query params, the text contains a link, a malformed id, or a bad start/end window.
401missing_bearer / invalid_keyNo key, or the key is unknown or revoked.
402subscription_requiredThe owning account is not on a plan or trial.
402 / 403insufficient_credits / lockedDraft endpoints: out of daily AI credits, or AI drafting has not been unlocked with enough hand-written replies yet.
403read_only_keyThe key is read-only and the endpoint writes (publish, schedule, reschedule, cancel, draft). Mint a read & write key.
404not_foundNo scheduled post with that id on this account.
400image_rejectedAn image_url could not be used: bad URL, blocked host, too large, or not an image.
409x_not_connected / x_token_expired / not_pendingReconnect X, or the post is no longer pending (already publishing or published).
429daily_post_cap_reachedHit the 5/day post cap. Resets 00:00 UTC.
429rate_limitedToo many GET requests this minute. Back off per the Retry-After header.
500image_store_failedCould not store a scheduled image; the post was rolled back.
502publish_failed / media_upload_failedX rejected the post or an image.