Skip to content
reelspanDocs

Get started

API overview

How the Reelspan API works - base URL, API keys, multipart and JSON bodies, response and error shapes, status codes, idempotency, rate limits, paging and ids.

The Reelspan API is plain HTTPS and JSON. This page covers the rules every endpoint shares; each endpoint's own page covers its fields.

Base URL

https://api.reelspan.com/v1

Every endpoint lives under /v1. The machine-readable OpenAPI document describes all of them, and the API reference is built from it.

Send API keys to api.reelspan.com. The dashboard's own address (app.reelspan.com) answers a request made with an API key with 421 and error_code: "USE_API_HOST", naming the right address.

Authentication

Send your key in the Authorization header on every request:

curl https://api.reelspan.com/v1/me \
  -H "Authorization: Bearer $REELSPAN_API_KEY"

Authorization: Apikey $REELSPAN_API_KEY works too, as Upload-Post clients send it. Create keys in the dashboard under Developers → API keys. A key can be limited in two ways:

  • Scopes: publish, read, profiles, analytics and webhooks. A call outside the key's scopes gets 403 with error_code: "INSUFFICIENT_SCOPE".
  • Profiles: a key limited to some profiles gets 403 PROFILE_FORBIDDEN for any other profile, and posts or jobs of other profiles answer 404 as if they didn't exist.

A missing, wrong, revoked or expired key gets 401. After 10 failed attempts from one IP address in 5 minutes, that address gets 429 with error_code: "AUTH_RATE_LIMITED" and Retry-After: 300. More in Authentication.

Request formats

Endpoint Body
POST /v1/posts/video, /photos, /document multipart/form-data: files and form fields together
POST /v1/posts/text, POST /v1/youtube/thumbnail JSON or multipart/form-data
POST /v1/media/upload multipart/form-data, one field named file
Everything else JSON body (Content-Type: application/json) or query parameters

In multipart forms:

  • Lists repeat the field with [] in its name: -F "platform[]=tiktok" -F "platform[]=youtube".
  • Booleans are the strings true or false.
  • Media can be a file or a public https URL in the same field, or the media_id of a file you uploaded earlier.

In JSON (text posts), send the list as "platform": ["x", "bluesky"]; "platform[]" works too.

Responses

A successful answer has "success": true and the data at the top level:

{ "success": true, "request_id": "9b1f6c2e8a3d4f50b7e1c9a2d6f3e8b1", "results": { "…": "…" } }

An error has "success": false, a readable message, and usually a machine-readable error_code to branch on. Some errors add fields, such as usage on a monthly-limit error or invalid_platforms when none of the networks can be used:

{ "success": false, "message": "This API key does not have the \"publish\" scope", "error_code": "INSUFFICIENT_SCOPE" }

An unexpected server error is { "success": false, "error": "Internal server error" } with 500. Errors lists the codes.

Per-network results

A post to several networks never fails as a whole because one network did. When a post finishes within the request, results holds one object per network:

{
  "success": true,
  "request_id": "9b1f6c2e8a3d4f50b7e1c9a2d6f3e8b1",
  "results": {
    "tiktok": { "success": true, "url": "https://www.tiktok.com/@mybrand/video/7441…", "post_id": "7441…" },
    "x": { "success": true, "url": "https://x.com/mybrand/status/18…", "post_id": "18…", "warnings": ["The text was posted as a thread of 2 posts."] },
    "pinterest": { "success": false, "error": "…", "error_code": "…" }
  },
  "usage": { "count": 41, "limit": null, "last_reset": "2026-11-01T00:00:00.000Z" }
}
  • success, url and post_id say where it went live.
  • error and error_code say why it didn't.
  • warnings lists anything changed on the way (links removed on X, a long text split into a thread, a first comment that couldn't be posted).
  • skipped: true with a skip_reason means the network was never tried, for example because the profile has no account on it. Skipped networks aren't charged.

The top-level success only says the request was accepted, so always read each network's own success. Post status returns the same results as a list.

Status codes

Code Meaning
200 Done. For a post: finished (with results) or moved to the background (with request_id).
201 Created (a profile, an approval link).
202 Scheduled or queued: the answer carries a job_id.
400 Something in the request is wrong; message says what.
401 No key, or the key is invalid, revoked or expired.
403 The key lacks the scope or the profile, or the workspace is suspended.
404 The profile, post, job or file doesn't exist (or the key can't see it).
409 A conflict: the same Idempotency-Key still running, a request_id already used, a scheduled post already publishing, nothing to retry, or an account that must be reconnected (reauth_required).
413 The file is larger than your plan allows.
421 An API key was sent to the dashboard's address instead of api.reelspan.com.
429 Rate limit, monthly upload limit, or the daily test-post limit.
500 Reelspan's fault. Safe to retry with an Idempotency-Key.
502 A network refused or didn't answer: a lookup (Pinterest boards, TikTok creator info, Facebook Page details…) or a change to a live post (unpublish, edit, repost). error_code says why.

Idempotency

Networks drop, and a retried POST could publish twice. Send an Idempotency-Key header (any string up to 200 characters, such as a UUID) on the four publish endpoints, and a repeat with the same key returns the first request's result instead of posting again:

curl -X POST https://api.reelspan.com/v1/posts/text \
  -H "Authorization: Bearer $REELSPAN_API_KEY" \
  -H "Idempotency-Key: 6f1c2a7e-weekly-tip-48" \
  -H "Content-Type: application/json" \
  -d '{"user": "my-brand", "platform": ["x"], "title": "Weekly tip"}'
  • A key is remembered for 24 hours, per workspace and per kind of post (video, photos, text, document).
  • A replay answers with the first request's current state, the header Idempotent-Replayed: true and "idempotent_replay": true in the body.
  • While the first request with a key is still open (including its wait of up to 59 seconds for results), a second one gets 409 IDEMPOTENCY_KEY_IN_USE. Wait a few seconds and send it again.
  • X-Idempotency-Key works as another name for the header.
  • X-Request-Id is a third name, used only when neither of the others is sent, and it also becomes the post's request_id (below).

You can also pick the post's id yourself: send request_id (8 to 100 letters, digits, _ or -), as a field or as the X-Request-Id header, and you can poll it even if the answer never arrived. A header in any other shape (a proxy's own trace id, say) is ignored. Reusing a request_id gets 409 REQUEST_ID_IN_USE. The SDKs add an Idempotency-Key to every post for you. See Retrying safely.

Rate limits

Each workspace has a request limit per minute and per 10 minutes, set by its plan (Plans and limits). Every answer to an API-key request carries the window closest to running out:

Header Meaning
X-RateLimit-Limit Requests allowed in the window
X-RateLimit-Remaining Requests left in it
X-RateLimit-Reset When it resets, in Unix seconds

Past the limit you get 429 with error_code: "RATE_LIMITED" and a Retry-After header in seconds. The monthly upload limit (Free plan) is separate: a post that would go over it gets 429 with your usage. See Rate limits.

Paging

List endpoints page in one of three ways:

Endpoint Parameters Done when
History GET /v1/posts page (from 1) and limit (10, 20, 50 or 100) page × limit ≥ total
Scheduled posts GET /v1/scheduled offset and limit (1 to 200, default 50) offset + limit ≥ total
Media library GET /v1/library limit (1 to 100) and before next_before is null; otherwise pass it as before

Webhook deliveries and approval links return the newest entries only, without pages.

Ids

Id Looks like What it names
request_id 9b1f6c2e8a3d4f50b7e1c9a2d6f3e8b1 (or your own) One post request, across all its networks. Use it for status, retry, history and post analytics.
job_id sched_5d0e… A scheduled or queued post. Use it to read, edit or cancel it. A scheduled post has a request_id too.
media_id med_… A file uploaded with Upload media, to post or keep in the library.
external_id yours Any id from your own system (up to 255 characters), sent with a post (as a field or the X-External-Id header) and returned in history, status and webhooks.

Test keys

A test key (rs_test_…) runs every check a live key does, then simulates the post: each network comes back success: true with a post_id that starts with test_ and no url, and status answers carry "test": true. Nothing reaches the networks and nothing counts against your plan. A workspace can send 500 test posts a day; past that the answer is 429 TEST_LIMIT.

A test post still needs the networks connected to the profile, as a real post would. To try things with nothing connected, post to the sandbox network. See Test mode.

Something unclear or missing? Write to [email protected] and we’ll fix the page.