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_keyThe 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/jsonon 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
429with aRetry-Afterheader. - Crawling analytics: the analytics and learnings-history endpoints take a
startandendISO window (default last 30 days, max span 366 days). Every response carriesgenerated_atandas_of(the newest metrics snapshot in the window) - poll on a schedule and dedupe onas_ofto 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
/api/v1/postsShips a post to X immediately through the connected account. Attach up to 4 images with image_urls.
| Field | Type | Description | |
|---|---|---|---|
| text | string | required | The post body. 1 to 10,000 chars. No URLs. |
| image_urls | string[] | optional | Up 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."}'{
"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
/api/v1/postsReturns the account’s recent published posts and their latest metrics snapshot.
| Field | Type | Description | |
|---|---|---|---|
| limit | number | optional | How many to return. 1 to 100, default 30. |
curl https://climbx.so/api/v1/posts?limit=10 \
-H "Authorization: Bearer climbx_sk_your_key"{
"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
/api/v1/scheduleQueues 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.
| Field | Type | Description | |
|---|---|---|---|
| text | string | required | The post body. 1 to 10,000 chars. No URLs. |
| scheduled_for | string | required | ISO 8601 datetime, e.g. 2026-06-01T14:00:00Z. |
| image_urls | string[] | optional | Up 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"}'{
"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
/api/v1/scheduleReturns upcoming posts that are still pending or mid-publish.
curl https://climbx.so/api/v1/schedule \
-H "Authorization: Bearer climbx_sk_your_key"{
"scheduled": [
{
"id": "3a2b...",
"text": "morning thought.",
"scheduled_for": "2026-06-01T08:00:00Z",
"status": "pending"
}
]
}Reschedule a pending post
/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).
| Field | Type | Description | |
|---|---|---|---|
| scheduled_for | string | required | New 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"}'{
"ok": true,
"scheduled": {
"id": "3a2b...",
"text": "morning thought.",
"scheduled_for": "2026-06-02T09:30:00Z",
"status": "pending"
}
}Cancel a scheduled post
/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"{
"ok": true,
"cancelled": { "id": "3a2b...", "status": "cancelled" }
}Performance summary
/api/v1/analyticsHeadline KPIs plus a per-format breakdown over a window. Replies are excluded.
| Field | Type | Description | |
|---|---|---|---|
| days | number | optional | Lookback window. 1 to 90, default 30. |
curl https://climbx.so/api/v1/analytics?days=30 \
-H "Authorization: Bearer climbx_sk_your_key"{
"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
/api/v1/analytics/formatsPer-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.
| Field | Type | Description | |
|---|---|---|---|
| start | string | optional | ISO datetime window start. Defaults to 30 days before end. |
| end | string | optional | ISO 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"{
"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
/api/v1/analytics/nichesSame shape as Format performance, bucketed by niche instead of format. Posts with no niche label come back under "__unlabeled__".
| Field | Type | Description | |
|---|---|---|---|
| start | string | optional | ISO datetime window start. Defaults to 30 days before end. |
| end | string | optional | ISO 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"{
"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
/api/v1/voiceThe 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"{
"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
/api/v1/learningsThe 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"{
"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
/api/v1/learnings/historyThe 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.
| Field | Type | Description | |
|---|---|---|---|
| start | string | optional | ISO datetime window start. Defaults to 30 days before end. |
| end | string | optional | ISO 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"{
"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
/api/v1/inspiration/optionsA 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"{
"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
/api/v1/inspiration/followingOutlier 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.
| Field | Type | Description | |
|---|---|---|---|
| handles | string | optional | Comma-separated handles to narrow to a subset of the creators you track. Omit for all of them. |
| limit | number | optional | Max 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"{
"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
/api/v1/inspiration/surpriseRandom 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.
| Field | Type | Description | |
|---|---|---|---|
| min_multiplier | number | optional | Floor on the outlier multiplier; snaps to 1.5, 2, 3, or 7. |
| min_impressions | number | optional | Floor on impressions; snaps to 0, 10000, 50000, or 100000. |
| format | string | optional | Restrict to one format (e.g. hot_take); all = no filter. |
| recency | string | optional | 7d or 30d to limit to recent posts; all = no floor. |
| image | string | optional | with = posts with media, without = text-only. |
| limit | number | optional | Max 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"{
"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
/api/v1/engage/suggest-replyGiven 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).
| Field | Type | Description | |
|---|---|---|---|
| text | string | required | The post to reply to. 1 to 4000 chars. |
| authorHandle | string | optional | The 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"}'{
"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.
| Status | error | When |
|---|---|---|
| 400 | invalid_body / invalid_query / url_posts_not_allowed / invalid_id / invalid_window | Bad payload or query params, the text contains a link, a malformed id, or a bad start/end window. |
| 401 | missing_bearer / invalid_key | No key, or the key is unknown or revoked. |
| 402 | subscription_required | The owning account is not on a plan or trial. |
| 402 / 403 | insufficient_credits / locked | Draft endpoints: out of daily AI credits, or AI drafting has not been unlocked with enough hand-written replies yet. |
| 403 | read_only_key | The key is read-only and the endpoint writes (publish, schedule, reschedule, cancel, draft). Mint a read & write key. |
| 404 | not_found | No scheduled post with that id on this account. |
| 400 | image_rejected | An image_url could not be used: bad URL, blocked host, too large, or not an image. |
| 409 | x_not_connected / x_token_expired / not_pending | Reconnect X, or the post is no longer pending (already publishing or published). |
| 429 | daily_post_cap_reached | Hit the 5/day post cap. Resets 00:00 UTC. |
| 429 | rate_limited | Too many GET requests this minute. Back off per the Retry-After header. |
| 500 | image_store_failed | Could not store a scheduled image; the post was rolled back. |
| 502 | publish_failed / media_upload_failed | X rejected the post or an image. |