cross-post API

Programmatically manage connected accounts, upload media, and create or schedule posts. Crosspost provides the dashboard and public API, with Bundle handling social publishing. New to Crosspost? Create an account and generate an API key in the dashboard.

Base URL

URL
https://cross-post.app/api/v1

All requests and responses use JSON. Set the Content-Type: application/json header on all requests with a body.

All API endpoints require authentication. See the Authentication section below to get started.

Supported Platforms

Crosspost supports Instagram, YouTube, TikTok, X (Twitter), Threads, Pinterest, Facebook, and LinkedIn. Availability and media requirements vary by platform.

Codex & Claude agents #

Let your agent produce a video with its own rendering tools, then upload and schedule the finished file through Crosspost. Video generation is not a Crosspost service. Provider writes require an active paid plan, current trial, or complimentary access; creating an API key does not grant these.

  1. Open Settings > Developer API, select Agent video scheduling, and create a named key. Save the key once in your agent's private environment as CROSSPOST_API_KEY, not in a prompt or source file.
  2. Download the Python 3 agent connector. It needs no additional packages. Use its command-line interface or run it with mcp for a local stdio MCP connection.
  3. Ask your agent to list accounts and check usage first. Select explicit local account IDs, your finished video, caption, and a future date/time with a UTC offset. Accounts in a single post must have the same account_group_id.
  4. Keep one job_id per scheduling intent. Reuse that job to resume interrupted work; its private receipt retains the file fingerprint, upload token and exact post idempotency request. A returned post ID means accepted/scheduled, not necessarily published.

The connector exposes list_accounts, get_usage, schedule_video, and get_post. It does not generate media, connect/disconnect accounts, cancel posts, or publish immediately. This first connector supports the confirmed-presigned-upload workflow used by live Bundle. MP4 and MOV are supported; WebM is not. Pinterest scheduling needs board options and is not supported by this connector yet. Known reconnect_required accounts are rejected before uploading. unknown authorization status is not proof of a working token.

Connect Codex

Add this to your Codex configuration, replacing the path with the absolute location where you downloaded the connector. Forward the API key through the local environment; keep it out of the configuration example. Official Codex MCP setup.

TOML
[mcp_servers.crosspost]
command = "python3"
args = ["/absolute/path/crosspost-agent.py", "mcp"]
env_vars = ["CROSSPOST_API_KEY"]
tool_timeout_sec = 900
default_tools_approval_mode = "writes"

Connect Claude Code

With CROSSPOST_API_KEY available in the launching environment, add a local server using the absolute connector path. Official Claude Code MCP setup. Cloud chat connectors cannot run this local stdio process.

Shell
claude mcp add --transport stdio crosspost -- python3 /absolute/path/crosspost-agent.py mcp

Agent prompt

“List my Crosspost accounts and confirm my upload allowance. Create a finished MP4 using my approved assets. Show me the accounts, caption and schedule, then schedule it for the time I approve. Preserve the job ID if anything is interrupted, and report the returned post ID.”

Command-line use

Shell
python3 crosspost-agent.py accounts
python3 crosspost-agent.py usage
python3 crosspost-agent.py schedule-video \
  --file /absolute/path/video.mp4 --accounts 4 5 \
  --at 2026-10-02T13:00:00+05:30 \
  --caption "Your approved caption" --job-id campaign-video-001
python3 crosspost-agent.py status 123

Replace the example account IDs, timestamp and post ID with your own. macOS and Linux are supported. Keep the same job ID and arguments when resuming. Receipts are private under ~/.local/state/crosspost-agent, or your owned mode-0700 CROSSPOST_STATE_DIR. Do not delete that state to bypass an uncertain result.

REST discovery and safety

GET /api/v1/capabilities accepts usage:read (or legacy analytics:read) and returns current media limits, allowed MIME types, confirmation requirements and same-group rules. GET /api/v1/accounts returns local account_group_id and authorization warnings. GET /api/v1/usage includes provider_write_allowed.

Use the five preset scopes only: accounts:read, usage:read, media:write, posts:write, posts:read. Existing scope permissions cover all owned accounts; this is not an account-restricted key, and posts:write also permits deletion through the REST API. The connector itself offers scheduling, not deletion.

An unknown upload-initialization outcome must not be blindly retried. The connector blocks that job for investigation. Post retries must preserve the same key and byte-identical JSON body. Bound post keys cannot be reused, even after the usual response TTL. Never send your Crosspost Bearer key to the storage upload URL.

Authentication #

Authenticate API requests using a Bearer token. Generate API keys from your cross-post dashboard under Settings > Developer API.

Include your API key in the Authorization header of every request:

curl
curl https://cross-post.app/api/v1/accounts \
  -H "Authorization: Bearer cp_live_xxxxx..."

API Key Format

API keys use the prefix cp_live_ followed by 40 hex characters. Keep your keys secret and never expose them in client-side code.

Scopes

Each API key can be assigned granular scopes that limit what it can access:

ScopeDescription
accounts:readList connected social accounts
accounts:writeConnect and disconnect social accounts
posts:readList and retrieve posts
posts:writeCreate and delete posts
media:writeUpload media files
analytics:readView analytics and usage data
usage:readView subscription usage and limits
webhooks:readList registered webhooks
webhooks:writeCreate and delete webhooks
Requesting an endpoint without the required scope returns a 403 Forbidden error with code insufficient_scope.

Rate Limiting #

API requests are rate-limited to 60 requests per minute per newly created API key. There is no dashboard rate-limit editor. Rate limit information is included in authenticated responses via headers:

HeaderDescription
X-RateLimit-LimitMaximum requests per window
X-RateLimit-RemainingRequests remaining in current window
X-RateLimit-ResetUnix timestamp when the window resets
Retry-AfterSeconds to wait before retrying (only present on 429 responses)

When you exceed the limit, the API returns 429 Too Many Requests. Use the Retry-After header to determine how long to wait before retrying.

Response — 429
{
  "error": {
    "code": "rate_limit_exceeded",
    "message": "Rate limit exceeded. Try again in 42 seconds",
    "status": 429
  }
}

Error Handling #

The API uses standard HTTP status codes and returns errors in a consistent format:

JSON
{
  "error": {
    "code": "validation_error",
    "message": "The caption field is required.",
    "status": 400
  }
}

Error Codes

CodeStatusDescription
auth_required401Missing or invalid Authorization header
invalid_api_key401API key is invalid or has been revoked
insufficient_scope403API key lacks the required scope for this endpoint
validation_error400Request body or parameters failed validation
unsupported_fanout422The selected provider does not support destinations spanning multiple provider profiles or teams
not_found404The requested resource does not exist
method_not_allowed405HTTP method not supported for this endpoint
rate_limit_exceeded429Too many requests — slow down
limit_exceeded403Account usage limit reached for your plan
late_api_error500An error occurred communicating with the upstream publishing service
internal_error500An unexpected error occurred on our end

Pagination #

List endpoints return paginated results. Control pagination with query parameters:

ParameterDefaultMaxDescription
page1—Page number
per_page20100Items per page

Paginated responses include a pagination object:

JSON
{
  "data": [...],
  "pagination": {
    "page": 1,
    "per_page": 20,
    "total": 47,
    "has_more": true
  }
}

List Accounts #

GET /api/v1/accounts accounts:read

Returns all social accounts connected to your cross-post account.

Each record also includes provider, local account_group_id, authorization_status, authorization_message and authorization_checked_at. Group IDs belong to your account; no upstream team identifiers are exposed. Use one group per Bundle post.

Response

JSON
{
  "data": [
    {
      "id": 12,
      "platform": "instagram",
      "username": "mycreatoraccount",
      "display_name": "My Creator Account",
      "avatar_url": "https://...",
      "connected_at": "2026-01-15 10:30:00",
      "is_active": true
    }
  ]
}

Example

curl
curl https://cross-post.app/api/v1/accounts \
  -H "Authorization: Bearer cp_live_xxxxx..."

Connect Account #

POST /api/v1/accounts/connect accounts:write

Generates an OAuth connect URL for the specified platform. Redirect the user to this URL to authorize the connection.

Request Body

FieldTypeRequiredDescription
platformstringrequiredOne of: instagram, youtube, tiktok, twitter, threads, pinterest, facebook, linkedin

Response

JSON
{
  "data": {
    "auth_url": "https://accounts.google.com/o/oauth2/v2/auth?...",
    "platform": "instagram"
  }
}

Example

curl
curl -X POST https://cross-post.app/api/v1/accounts/connect \
  -H "Authorization: Bearer cp_live_xxxxx..." \
  -H "Content-Type: application/json" \
  -d '{"platform": "instagram"}'

Disconnect Account #

DELETE /api/v1/accounts/{id} accounts:write

Disconnects a social account. Any scheduled posts targeting this account will fail.

Path Parameters

ParameterDescription
idThe account ID (integer, e.g., 12)

Response

JSON
{
  "data": {
    "success": true
  }
}

Example

curl
curl -X DELETE https://cross-post.app/api/v1/accounts/12 \
  -H "Authorization: Bearer cp_live_xxxxx..."

List Posts #

GET /api/v1/posts posts:read

Returns a paginated list of your posts. Supports filtering by status and platform.

Query Parameters

ParameterTypeDefaultDescription
statusstring—Filter by status: draft, scheduled, publishing, published, partial, failed
platformstring—Filter by target platform (e.g., instagram)
pageinteger1Page number
per_pageinteger20Results per page (max 100)

Response

JSON
{
  "data": [
    {
      "id": 42,
      "caption": "Check out our new feature!",
      "status": "published",
      "publish_mode": "now",
      "scheduled_at": null,
      "published_at": "2026-03-15 14:22:00",
      "created_at": "2026-03-15 14:20:00",
      "media": [
        {
          "url": "https://cdn.cross-post.app/media/abc123.jpg",
          "filename": "abc123.jpg",
          "mime_type": "image/jpeg",
          "size": 284720,
          "sort_order": 0
        }
      ],
      "destinations": [
        {
          "account_id": 12,
          "platform": "instagram",
          "status": "published",
          "published_at": "2026-03-15 14:22:00",
          "error_message": null
        }
      ]
    }
  ],
  "pagination": {
    "page": 1,
    "per_page": 20,
    "total": 47,
    "has_more": true
  }
}

Get Post #

GET /api/v1/posts/{id} posts:read

Retrieves a single post by ID, including its media and per-destination status.

Response

Returns the same post object shape as the list endpoint, wrapped in a data key (not an array).

Example

curl
curl https://cross-post.app/api/v1/posts/42 \
  -H "Authorization: Bearer cp_live_xxxxx..."

Create Post #

POST /api/v1/posts posts:write

Creates a new post and publishes it to the specified social accounts. Upload media first using the media upload endpoint. When that response sets requires_confirm, complete the confirmation step before creating the post.

Account groups: You can connect several accounts on one platform within your plan allowance. Each belongs to an owned group, and every post must stay in one group, including text-only posts. Use local account_group_id metadata from the accounts endpoint; never send upstream team IDs. Upload separately for each group and pass the finalized media_id with its confirmed URL. URL-only media imports and cross-group fan-out are not supported.

Request Body

FieldTypeRequiredDescription
captionstringoptionalPost caption / text content
media_urlsarrayoptionalHTTPS URL strings, or objects with url, type, and confirmed media_id. When Bundle is selected, URL strings and objects without a finalized media_id are rejected; use the upload and confirmation endpoints first.
destination_account_idsinteger[]requiredAccount IDs to publish to (integers)
publish_modestringoptionalnow, schedule, or draft. Defaults to schedule.
scheduled_atstringif scheduleDatetime string (e.g., 2026-03-20T15:00:00). Must be in the future.
timezonestringoptionalIANA timezone (e.g., America/New_York). Defaults to UTC.

Idempotency

The X-Idempotency-Key header is required. Save one key and the exact JSON bytes per intended post before sending it. Reuse both on uncertain retries; a changed body is a conflict. Keys bound to a local post are retained and cannot be reused even after 24 hours. Keep completed job receipts indefinitely and read the existing post rather than starting another creation.

Response

JSON
{
  "data": {
    "id": 57,
    "caption": "Check out our new feature!",
    "status": "scheduled",
    "publish_mode": "schedule",
    "scheduled_at": "2026-03-20T15:00:00",
    "published_at": null,
    "created_at": "2026-03-17 10:00:00",
    "media": [
      {
        "url": "https://cdn.cross-post.app/media/abc123.jpg",
        "filename": "abc123.jpg",
        "mime_type": null,
        "size": null,
        "sort_order": 0
      }
    ],
    "destinations": [
      {
        "account_id": 12,
        "platform": "instagram",
        "status": "pending",
        "published_at": null,
        "error_message": null
      },
      {
        "account_id": 15,
        "platform": "twitter",
        "status": "pending",
        "published_at": null,
        "error_message": null
      }
    ]
  }
}

Example

curl
curl -X POST https://cross-post.app/api/v1/posts \
  -H "Authorization: Bearer cp_live_xxxxx..." \
  -H "Content-Type: application/json" \
  -H "X-Idempotency-Key: saved-unique-post-intent" \
  -d '{
    "caption": "Check out our new feature!",
    "destination_account_ids": [12, 15],
    "publish_mode": "schedule",
    "scheduled_at": "2026-10-02T15:00:00Z",
    "timezone": "UTC"
  }'

Delete Post #

DELETE /api/v1/posts/{id} posts:write

Deletes a post. Only posts with status draft, scheduled, or failed can be deleted. Published or publishing posts cannot be deleted.

Response

JSON
{
  "data": {
    "success": true
  }
}

Example

curl
curl -X DELETE https://cross-post.app/api/v1/posts/42 \
  -H "Authorization: Bearer cp_live_xxxxx..."

Upload Media #

POST /api/v1/media/upload media:write

Returns a presigned upload URL. Upload the file with PUT. If requires_confirm is true, finalize it through /api/v1/media/confirm; otherwise the returned public_url is ready immediately.

Request Body

FieldTypeRequiredDescription
filenamestringrequiredOriginal filename with extension
content_typestringrequiredMIME type (e.g., image/jpeg, video/mp4)
size_bytesintegerrequiredExact file size in bytes. Files above the account plan limit are rejected before an upload URL is issued.
destination_account_idintegerconditionalLocal connected-account ID from GET /api/v1/accounts. Required when your accounts span multiple account groups; optional with a single group. Never supply an upstream team ID.

Multiple Instagram accounts: add "destination_account_id": 123 to upload requests, using the account you intend to publish to. Uploads stay bound to that account group. A post may select several platforms within the same group, but cannot publish across groups; upload separately for another Instagram account. Omitting the selector when several groups exist returns 409 conflict.

Response

JSON
{
  "data": {
    "upload_url": "https://storage.cross-post.app/presigned/...",
    "public_url": null,
    "media_id": "opaque_upload_token",
    "requires_confirm": true
  }
}

Upload Flow

Step 1: Call this endpoint.
Step 2: PUT the raw file to upload_url with the matching Content-Type.
Step 3: If requires_confirm is true, call the confirmation endpoint with media_id and the same byte size. A 202 response means the provider is still processing; retry with the newly returned media_id. Create the post only after ready is true and public_url is non-null.

Example

curl
# Step 1: Get presigned URL
curl -X POST https://cross-post.app/api/v1/media/upload \
  -H "Authorization: Bearer cp_live_xxxxx..." \
  -H "Content-Type: application/json" \
  -d '{"filename": "photo.jpg", "content_type": "image/jpeg", "size_bytes": 245760}'

# Step 2: Upload the file
curl -X PUT "https://storage.cross-post.app/presigned/..." \
  -H "Content-Type: image/jpeg" \
  --data-binary @photo.jpg

# Step 3 when requires_confirm is true
curl -X POST https://cross-post.app/api/v1/media/confirm \
  -H "Authorization: Bearer cp_live_xxxxx..." \
  -H "Content-Type: application/json" \
  -d '{"media_id": "opaque_upload_token", "size_bytes": 245760}'

Confirm Media #

POST /api/v1/media/confirm media:write

Finalizes a provider upload. Call this only when the upload response sets requires_confirm to true.

Request Body

FieldTypeRequiredDescription
media_idstringrequiredOpaque token returned by the latest upload or confirmation response
size_bytesintegeroptionalExact uploaded file size; when provided it must match initialization

Ready Response — 200

{
  "data": {
    "media_id": "confirmed_media_token",
    "public_url": "https://cdn.example/media/photo.jpg",
    "ready": true,
    "requires_confirm": false
  }
}

Still Processing — 202

Retry this endpoint with the newly returned media_id. The token is designed for safe retries and does not repeat provider finalization.

{
  "data": {
    "media_id": "pending_finalized_token",
    "public_url": null,
    "ready": false,
    "requires_confirm": true
  }
}

Get Analytics #

GET /api/v1/analytics analytics:read

Returns analytics for your account, including post counts, platform breakdown, and daily posting trends.

Query Parameters

ParameterTypeDefaultDescription
periodstring30dTime period: 7d, 30d, 90d, or all

Response

JSON
{
  "data": {
    "period": "30d",
    "total_posts": 142,
    "published_count": 128,
    "failed_count": 3,
    "scheduled_count": 11,
    "by_status": {
      "published": 128,
      "failed": 3,
      "scheduled": 11
    },
    "platforms": {
      "instagram": 45,
      "twitter": 38,
      "youtube": 22,
      "tiktok": 18,
      "threads": 12,
      "bluesky": 7
    },
    "success_rate": 97.7,
    "posts_over_time": [
      { "date": "2026-03-01", "count": 5 },
      { "date": "2026-03-02", "count": 3 }
    ]
  }
}

Get Usage #

GET /api/v1/usage usage:read

Returns current usage counts, subscription tier limits and provider_write_allowed. A limit value of -1 means unlimited. Use usage:read; analytics:read is accepted for older keys.

Response

JSON
{
  "data": {
    "subscription_tier": "pro",
    "subscription_expires_at": "2026-04-15 00:00:00",
    "posts": {
      "used": 42,
      "limit": 500,
      "remaining": 458
    },
    "scheduled_posts": {
      "used": 8,
      "limit": 100,
      "remaining": 92
    },
    "social_accounts": {
      "used": 4,
      "limit": 15,
      "remaining": 11
    },
    "max_media_size_mb": 100
  }
}

List Webhooks #

GET /api/v1/webhooks webhooks:read

Returns all active webhook endpoints for your account. The secret is not included in list responses.

Response

JSON
{
  "data": [
    {
      "id": 3,
      "url": "https://example.com/webhooks/crosspost",
      "events": ["post_published", "post_failed"],
      "created_at": "2026-02-10 08:00:00"
    }
  ]
}

Create Webhook #

POST /api/v1/webhooks webhooks:write

Registers a new webhook endpoint. You will receive a secret in the response — store it securely for signature verification. Maximum 5 webhooks per user.

Request Body

FieldTypeRequiredDescription
urlstringrequiredHTTPS URL to receive webhook payloads
eventsstring[]requiredEvents to subscribe to (see events list)

Response

JSON
{
  "data": {
    "id": 3,
    "url": "https://example.com/webhooks/crosspost",
    "events": ["post_published", "post_failed"],
    "secret": "a1b2c3d4e5f6...",
    "created_at": "2026-03-17 10:00:00"
  }
}
The secret is only returned once at creation time. Store it securely — you will need it to verify webhook signatures.

Example

curl
curl -X POST https://cross-post.app/api/v1/webhooks \
  -H "Authorization: Bearer cp_live_xxxxx..." \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/webhooks/crosspost",
    "events": ["post_published", "post_failed"]
  }'

Delete Webhook #

DELETE /api/v1/webhooks/{id} webhooks:write

Deletes a registered webhook. No further events will be delivered to this endpoint.

Response

JSON
{
  "success": true
}

Example

curl
curl -X DELETE https://cross-post.app/api/v1/webhooks/3 \
  -H "Authorization: Bearer cp_live_xxxxx..."

Webhook Events & Signatures #

Available Events

EventDescription
post_createdA new post was created (any publish mode)
post_publishedA post was successfully published to all destinations
post_failedA post failed to publish on one or more destinations
account_connectedA new social account was connected
account_disconnectedA social account was disconnected

Delivery

Webhooks are delivered as POST requests to your registered URL with a JSON payload. Respond with a 2xx status code within 10 seconds to acknowledge receipt.

Retry Policy

Failed deliveries are retried up to 5 times with exponential backoff: 30 seconds, 2 minutes, 8 minutes, 32 minutes, 2 hours.

Payload Format

The webhook body is a JSON object with event, data, and timestamp fields. The data contents vary by event type.

JSON — post_created
{
  "event": "post_created",
  "data": {
    "post_id": 57,
    "status": "scheduled",
    "publish_mode": "schedule",
    "platform_count": 2
  },
  "timestamp": "2026-03-17T14:30:00+00:00"
}
JSON — account_disconnected
{
  "event": "account_disconnected",
  "data": {
    "account_id": 12,
    "platform": "instagram"
  },
  "timestamp": "2026-03-17T14:30:00+00:00"
}

Signature Verification

Every webhook delivery includes these headers:

HeaderDescription
X-Webhook-SignatureHMAC-SHA256 signature of the raw request body: sha256={hex}
X-Webhook-EventThe event type (e.g., post_created)
User-Agentcross-post-webhooks/1.0
Content-Typeapplication/json

The signature is computed over the raw JSON body using your webhook secret as the HMAC key. Always verify signatures to ensure payloads are authentic.

The header format is: sha256={hex-encoded HMAC}

Node.js

JavaScript
const crypto = require('crypto');

function verifyWebhookSignature(payload, signature, secret) {
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(payload, 'utf8')
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

// Express middleware example
app.post('/webhooks/crosspost', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-webhook-signature'];
  const isValid = verifyWebhookSignature(req.body, signature, process.env.WEBHOOK_SECRET);

  if (!isValid) {
    return res.status(401).send('Invalid signature');
  }

  const event = JSON.parse(req.body);
  console.log('Received event:', event.event);
  res.sendStatus(200);
});

Python

Python
import hmac
import hashlib

def verify_webhook_signature(payload: bytes, signature: str, secret: str) -> bool:
    expected = 'sha256=' + hmac.new(
        secret.encode('utf-8'),
        payload,
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(signature, expected)

# Flask example
from flask import Flask, request, abort

app = Flask(__name__)

@app.route('/webhooks/crosspost', methods=['POST'])
def handle_webhook():
    signature = request.headers.get('X-Webhook-Signature', '')
    if not verify_webhook_signature(request.data, signature, WEBHOOK_SECRET):
        abort(401)

    event = request.get_json()
    print(f"Received event: {event['event']}")
    return '', 200

Code Examples #

Scheduling a video with an agent

Use the agent connector for the complete file-upload, confirmation and durable scheduling workflow. It preserves progress across interruptions. The basic REST sequence is:

  1. GET /accounts, GET /usage and GET /capabilities: choose owned account IDs in one group and verify current entitlement, byte limits and supported media types.
  2. POST /media/upload: send filename, MIME type, exact size_bytes and one selected destination_account_id. Save the response privately before continuing. Do not repeat an uncertain initialization.
  3. PUT the actual bytes to upload_url using upload_headers. Never send the Bearer key to storage. An expired URL is not permission to create another upload blindly.
  4. POST /media/confirm: return media_id and exact size. HTTP 202 means processing. Persist each returned media_id before another confirmation; do not create a post until ready=true and a confirmed public URL are present.
  5. POST /posts: send confirmed media URL/token, destination IDs, publish_mode="schedule", future offset-aware timestamp and timezone="UTC". Save the exact serialized JSON and X-Idempotency-Key before sending.
  6. Save the returned post ID and use GET /posts/{id} to observe status. A network error or reconciliation_required is not permission to create another post.

Minimal text-only REST scheduling

Prepare a private post.json containing your caption, selected account IDs, schedule mode, future ISO timestamp with offset, and UTC timezone. The same-group rule still applies. Store a unique key for this job as CROSSPOST_POST_KEY; keep that value and post.json unchanged for retries. These commands assume the API key is already in your private environment.

Shell
curl --fail-with-body https://cross-post.app/api/v1/posts \
  -H "Authorization: Bearer $CROSSPOST_API_KEY" \
  -H "Content-Type: application/json" \
  -H "X-Idempotency-Key: $CROSSPOST_POST_KEY" \
  --data-binary @post.json

Listing Accounts

Bash
curl https://cross-post.app/api/v1/accounts \
  -H "Authorization: Bearer cp_live_xxxxx..."
JavaScript
const res = await fetch('https://cross-post.app/api/v1/accounts', {
  headers: { 'Authorization': `Bearer ${API_KEY}` },
});
const { data: accounts } = await res.json();

accounts.forEach(account => {
  console.log(`${account.platform}: ${account.username} (${account.is_active ? 'active' : 'inactive'})`);
});
Python
import requests

res = requests.get('https://cross-post.app/api/v1/accounts',
    headers={'Authorization': f'Bearer {API_KEY}'}
)
accounts = res.json()['data']

for account in accounts:
    status = 'active' if account['is_active'] else 'inactive'
    print(f"{account['platform']}: {account['username']} ({status})")

Setting Up a Webhook

Bash
curl -X POST https://cross-post.app/api/v1/webhooks \
  -H "Authorization: Bearer cp_live_xxxxx..." \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/webhooks/crosspost",
    "events": [
      "post_published",
      "post_failed",
      "account_connected"
    ]
  }'
JavaScript
const res = await fetch('https://cross-post.app/api/v1/webhooks', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    url: 'https://example.com/webhooks/crosspost',
    events: ['post_published', 'post_failed', 'account_connected'],
  }),
});

const { data: webhook } = await res.json();
console.log('Webhook ID:', webhook.id);
console.log('Secret (store this!):', webhook.secret);
Python
import requests

res = requests.post('https://cross-post.app/api/v1/webhooks',
    headers={'Authorization': f'Bearer {API_KEY}'},
    json={
        'url': 'https://example.com/webhooks/crosspost',
        'events': ['post_published', 'post_failed', 'account_connected'],
    }
)

webhook = res.json()['data']
print(f"Webhook ID: {webhook['id']}")
print(f"Secret (store this!): {webhook['secret']}")

Need help? Open Support chat or email kolagames07@gmail.com.

Back to cross-post · Read the blog