Getting Started

Get started with Outstand, the unified social media API for connecting accounts, publishing posts, and building a social media scheduler across platforms.

Outstand offers a unified API to access a variety of social media platforms. Our ambition is to provide a simple to use API that covers most of the use cases you might need.

Core Features

  • Social Accounts: Connect your social media accounts to our API.
  • Posts: Create and manage your posts.
  • Scheduling: Schedule your posts to be published at a later time.
  • First comment scheduling: Schedule your first comment to be published at a later time, as long as it's supported by the social network.
  • Media: Attach media to your posts, video or images

Supported Platforms

One integration, eleven networks. Outstand supports the following platforms — use the canonical value in the left column wherever the API expects a network:

Platformnetwork valueConnect via
X (Twitter)xOAuth
LinkedInlinkedinOAuth
InstagraminstagramOAuth
FacebookfacebookOAuth
ThreadsthreadsOAuth
TikToktiktokOAuth
YouTubeyoutubeOAuth
PinterestpinterestOAuth
Google Business Profilegoogle_businessOAuth
VimeovimeoOAuth
BlueskyblueskyApp password

All platforms except Bluesky connect through the same OAuth flow (see the quickstart below). Bluesky uses an app password instead of OAuth.

Creating an account

To sign up for an account, you can go to the Outstand website and click on the "Sign Up" button.

Creating an account is free, no credit card is required and no invoices or other usage based billing is incurred.

Get an API Key

After signing up, you will be redirected to the main app dashboard.

From the dashboard, you can generate an API key.

This API key is used to authenticate your requests to the Outstand API.

Using the API

To use the API, you can pass the API key in the Authorization header of your requests.

Read more about authentication options and how to use your API key in the authentication section.

Developer Quickstart

Everything you need to go from zero to a published post on one page: connect an account, upload media, and create a post. Every request uses Authorization: Bearer YOUR_API_KEY.

Estimated integration timeline

Outstand replaces per-platform SDKs, OAuth apps, and publishing quirks with one API, so a first integration is fast. A realistic timeline for a small team:

MilestoneTypical effort
Sign up, generate an API key, first authenticated call~5 minutes
Connect your first social account via OAuth~30 minutes
Publish your first postunder 1 hour
Add media upload + scheduling~half a day
Production-ready (outcome polling, retries, error handling)1–2 days

Most teams have a working prototype in an afternoon and ship to production within two days.

1. Connect an account

Connecting is a browser OAuth redirect — you send the user to Outstand's authorize URL for a network, they approve, and Outstand calls you back with the new account. Start the flow by sending the user to:

https://www.outstand.so/app/api/socials/{network}/{orgId}?redirect_uri=https://yourapp.com/callback

Replace {network} with a value from Supported Platforms (e.g. x) and {orgId} with your organization ID. After the user approves, Outstand redirects back to your redirect_uri with the result as query params:

https://yourapp.com/callback?success=true&account_id=acc_123&network_unique_id=1780…&username=brand

Store the returned account_id — it's how you target this account when posting. On failure the callback carries an error param instead.

Bluesky doesn't use OAuth; connect it with an app password:

curl -X POST https://api.outstand.so/v1/social-accounts/bluesky \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "handle": "brand.bsky.social",
    "app_password": "xxxx-xxxx-xxxx-xxxx"
  }'

Confirm your connected accounts any time:

curl -X GET https://api.outstand.so/v1/social-accounts \
  -H "Authorization: Bearer YOUR_API_KEY"

2. Upload media

Media upload is a two-step presigned-URL flow: ask for an upload URL, then PUT the file to it, then confirm. Uploaded files live in storage for 60 days.

# Step 1 — request an upload URL
curl -X POST https://api.outstand.so/v1/media/upload \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "filename": "launch.jpg", "content_type": "image/jpeg" }'
{
  "success": true,
  "data": {
    "id": "med_abc",
    "upload_url": "https://…r2…/med_abc?X-Amz-Signature=…",
    "expires_in": 3600
  }
}
# Step 2 — PUT the raw file bytes to the returned upload_url
curl -X PUT "PASTE_UPLOAD_URL_HERE" \
  -H "Content-Type: image/jpeg" \
  --data-binary @launch.jpg

# Step 3 — confirm the upload to activate it
curl -X POST https://api.outstand.so/v1/media/med_abc/confirm \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "size": 82451 }'

The confirm response returns the public url you attach to a post:

{
  "id": "med_abc",
  "filename": "launch.jpg",
  "url": "https://media.outstand.so/org_abc/med_abc.jpg",
  "content_type": "image/jpeg",
  "size": 82451,
  "status": "active",
  "expires_at": "2026-09-07T12:00:00Z"
}

3. Create a post

Publish to one or more platforms in a single call. accounts is required and accepts an account ID, a network name, or a username. Attach media by passing its url and a filename on the container:

curl -X POST https://api.outstand.so/v1/posts/ \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "containers": [
      {
        "content": "Our Q2 launch is live 🚀",
        "media": [
          { "url": "https://media.outstand.so/org_abc/med_abc.jpg", "filename": "launch.jpg" }
        ]
      }
    ],
    "accounts": ["x", "linkedin"]
  }'
{
  "success": true,
  "post": {
    "id": "9dyJS",
    "publishedAt": null,
    "scheduledAt": null,
    "socialAccounts": [
      { "nickname": "Brand X", "network": "x", "username": "brand" },
      { "nickname": "Brand LI", "network": "linkedin", "username": "brand" }
    ],
    "containers": [
      { "id": "aZ1", "content": "Our Q2 launch is live 🚀", "media": [{ "id": 1, "url": "https://media.outstand.so/org_abc/med_abc.jpg", "filename": "launch.jpg" }] }
    ]
  }
}

To schedule instead of publishing now, add a scheduledAt timestamp — see Building a Social Media Scheduler below.

Quick Start

Once you have your API key, you can start using the Outstand API right away. Below are examples covering the most common workflows.

List Connected Social Accounts

Retrieve all social accounts connected to your organization:

curl -X GET https://api.outstand.so/v1/social-accounts \
  -H "Authorization: Bearer YOUR_API_KEY"

Create and Publish a Post

Post to multiple platforms with a single API call. The accounts array accepts an account ID, a network name (e.g. "x", "linkedin"), or a username — Outstand resolves each identifier to the matching connected account(s):

curl -X POST https://api.outstand.so/v1/posts/ \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Hello from Outstand! 🚀",
    "accounts": ["x", "linkedin"]
  }'

Schedule a Post

Use the scheduledAt field to publish at a specific time in the future:

curl -X POST https://api.outstand.so/v1/posts/ \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Scheduled post from Outstand",
    "accounts": ["acc_123"],
    "scheduledAt": "2026-04-01T09:00:00Z"
  }'

Building a Social Media Scheduler

A scheduler is one of the most common things developers build on top of Outstand. This section walks through the whole loop end to end — authenticate, schedule posts into your own queue, publish, and interpret the outcomes — with copy-paste code for each step.

How it works

Outstand is stateless about your scheduling logic and stateful about delivery. You decide when each post should go out (your cron, your calendar, your posting slots) and hand Outstand a single scheduledAt timestamp per post. Outstand stores the post, holds it, and publishes it at that time — fanning out to every target account and tracking a publish outcome per account.

The mental model, in four steps:

  1. Authenticate every request with your API key (Authorization: Bearer …).
  2. Schedule posts by creating them with a future scheduledAt. Each post is your queue entry. There is no separate "queue" resource — a scheduled post is a queued post.
  3. Poll the posts list to see what is coming up or has already gone out.
  4. Interpret outcomes by reading each post's per-account status (pending, published, failed, deleted) and the error / platformPostId fields.

Scheduling rules: scheduledAt is an ISO 8601 timestamp (UTC recommended, e.g. 2026-04-01T09:00:00Z). Omit it — or pass a time in the past — and the post publishes immediately. The furthest you can schedule is 30 days into the future. To build a longer-horizon calendar, keep the schedule in your own store and enqueue each post with Outstand as its send time comes within the 30-day window.

Step 1 — Authenticate

Every request carries your API key as a Bearer token. A quick call to /v1/social-accounts both verifies the key and returns the accounts you can post to:

curl -X GET https://api.outstand.so/v1/social-accounts \
  -H "Authorization: Bearer YOUR_API_KEY"

A missing or invalid key returns 401 with { "error": "Missing or invalid Authorization header" }.

Step 2 — Schedule a post into your queue

Create a post with a future scheduledAt. This is how you enqueue. The accounts array is required and controls the fan-out; to attach a first comment (where the network supports it) add extra containers — the first container is the post, each additional container is published as a reply to it.

curl -X POST https://api.outstand.so/v1/posts/ \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "containers": [
      {
        "content": "Our Q2 launch is live 🚀 Here is everything that shipped.",
        "media": [
          { "url": "https://cdn.example.com/launch.jpg", "filename": "launch.jpg" }
        ]
      },
      { "content": "Full changelog in the thread 👇 https://example.com/changelog" }
    ],
    "accounts": ["x", "linkedin", "instagram"],
    "scheduledAt": "2026-04-01T09:00:00Z"
  }'

The response confirms the queued post and echoes back the resolved accounts and containers:

{
  "success": true,
  "post": {
    "id": "9dyJS",
    "orgId": "org_abc",
    "publishedAt": null,
    "scheduledAt": "2026-04-01T09:00:00Z",
    "isDraft": false,
    "createdAt": "2026-03-20T12:00:00Z",
    "socialAccounts": [
      { "nickname": "Brand X", "network": "x", "username": "brand" },
      { "nickname": "Brand LI", "network": "linkedin", "username": "brand" }
    ],
    "containers": [
      { "id": "aZ1", "content": "Our Q2 launch is live 🚀 ...", "media": [{ "id": 1, "url": "https://cdn.example.com/launch.jpg", "filename": "launch.jpg" }] },
      { "id": "aZ2", "content": "Full changelog in the thread 👇 ...", "media": [] }
    ]
  }
}

Save post.id — it is the handle you use to check status, cancel, or reschedule.

Step 3 — List what's queued

Read your queue with GET /v1/posts. Filter by scheduled-time window to show only what is upcoming, and page with limit / offset:

curl -X GET "https://api.outstand.so/v1/posts?scheduled_after=2026-03-20T00:00:00Z&scheduled_before=2026-04-30T00:00:00Z&limit=50&offset=0" \
  -H "Authorization: Bearer YOUR_API_KEY"

Available query params: scheduled_after, scheduled_before, created_after, created_before (all ISO 8601), social_account_id, limit (1–100, default 50), and offset. Results are ordered newest-created first inside a pagination envelope:

{
  "success": true,
  "data": [ /* posts */ ],
  "posts": [ /* same posts, for backward compatibility */ ],
  "pagination": { "limit": 50, "offset": 0, "total": 12 }
}

A post is queued/pending while scheduledAt is set and publishedAt is still null. Once it goes out, publishedAt is populated.

Step 4 — Interpret the outcome

Fetch a single post to see the per-account result. Outstand publishes to each target independently, so a post can partially succeed — read status on every entry rather than assuming the post as a whole passed or failed:

curl -X GET https://api.outstand.so/v1/posts/9dyJS \
  -H "Authorization: Bearer YOUR_API_KEY"
{
  "success": true,
  "post": {
    "id": "9dyJS",
    "publishedAt": "2026-04-01T09:00:03Z",
    "scheduledAt": "2026-04-01T09:00:00Z",
    "socialAccounts": [
      { "network": "x", "username": "brand", "status": "published", "platformPostId": "1780000000000000000", "error": null, "publishedAt": "2026-04-01T09:00:03Z" },
      { "network": "linkedin", "username": "brand", "status": "failed", "platformPostId": null, "error": "Token expired — reconnect the account", "publishedAt": null }
    ],
    "containers": [
      {
        "id": "aZ2",
        "content": "Full changelog in the thread 👇 ...",
        "publishResults": [
          { "accountId": "acc_x", "status": "published", "platformCommentId": "1780000000000000001", "error": null, "publishedAt": "2026-04-01T09:00:05Z" }
        ]
      }
    ]
  }
}

Per-account status values:

StatusMeaning
pendingCreated/scheduled, awaiting publish. This is your "queued" state.
publishedDelivered successfully — platformPostId holds the native post ID.
failedPublish failed — read error for the reason (e.g. expired token).
deletedThe post was removed from the platform.

First comments carry their own outcomes in each container's publishResults array (status of published or failed, plus platformCommentId / error).

Reschedule or cancel a queued post

There is no in-place reschedule endpoint. To move a scheduled post, cancel it and create a new one with the new time. Cancelling deletes the pending post and removes it from the publishing pipeline:

curl -X DELETE https://api.outstand.so/v1/posts/9dyJS \
  -H "Authorization: Bearer YOUR_API_KEY"

Full scheduler loop in TypeScript

A minimal scheduler: resolve accounts, enqueue a post at a slot time, then poll until every account resolves to a terminal outcome.

const API_KEY = process.env.OUTSTAND_API_KEY!;
const BASE_URL = 'https://api.outstand.so';
const auth = { Authorization: `Bearer ${API_KEY}` };

// 1. Authenticate + discover accounts
const accountsRes = await fetch(`${BASE_URL}/v1/social-accounts`, { headers: auth });
const { data: accounts } = await accountsRes.json();

// 2. Schedule a post into your queue (a slot your own scheduler decided on)
const scheduledAt = new Date(Date.now() + 60 * 60 * 1000).toISOString(); // 1 hour out
const createRes = await fetch(`${BASE_URL}/v1/posts/`, {
  method: 'POST',
  headers: { ...auth, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    content: 'Scheduled with Outstand ⏰',
    accounts: accounts.map((a: any) => a.id),
    scheduledAt,
  }),
});
const { post } = await createRes.json();
console.log('Queued post:', post.id, 'for', post.scheduledAt);

// 3. Later: poll the outcome and interpret per-account status
const outcomeRes = await fetch(`${BASE_URL}/v1/posts/${post.id}`, { headers: auth });
const { post: result } = await outcomeRes.json();

for (const acc of result.socialAccounts) {
  if (acc.status === 'published') {
    console.log(`✅ ${acc.network}: ${acc.platformPostId}`);
  } else if (acc.status === 'failed') {
    console.error(`❌ ${acc.network}: ${acc.error}`);
  } else {
    console.log(`⏳ ${acc.network}: still ${acc.status}`);
  }
}

Full scheduler loop in Python

The same flow using Python and the requests library:

import os
import requests
from datetime import datetime, timedelta, timezone

API_KEY = os.environ["OUTSTAND_API_KEY"]
BASE_URL = "https://api.outstand.so"
auth = {"Authorization": f"Bearer {API_KEY}"}

# 1. Authenticate + discover accounts
accounts = requests.get(f"{BASE_URL}/v1/social-accounts", headers=auth).json()["data"]

# 2. Schedule a post into your queue
scheduled_at = (datetime.now(timezone.utc) + timedelta(hours=1)).isoformat()
post = requests.post(
    f"{BASE_URL}/v1/posts/",
    headers={**auth, "Content-Type": "application/json"},
    json={
        "content": "Scheduled with Outstand ⏰",
        "accounts": [a["id"] for a in accounts],
        "scheduledAt": scheduled_at,
    },
).json()["post"]
print(f"Queued post {post['id']} for {post['scheduledAt']}")

# 3. Later: poll the outcome and interpret per-account status
result = requests.get(f"{BASE_URL}/v1/posts/{post['id']}", headers=auth).json()["post"]
for acc in result["socialAccounts"]:
    if acc["status"] == "published":
        print(f"✅ {acc['network']}: {acc['platformPostId']}")
    elif acc["status"] == "failed":
        print(f"❌ {acc['network']}: {acc['error']}")
    else:
        print(f"⏳ {acc['network']}: still {acc['status']}")

Pricing & ROI

Outstand bills on a simple model: a flat monthly fee that includes a bundle of posts, plus metered pricing for anything beyond it. There are no per-platform fees and no separate charge for scheduling or first comments — a post is a post whether it fans out to one network or all eleven.

The figures below reflect the public pricing calculator and are here to help you estimate quickly. See our landing page for authoritative, current numbers.

Illustrative model: $19/mo base including 3,000 posts, then $0.007/post for posts 3,001–10,000, and $0.005/post beyond 10,000.

Worked examples

Monthly postsHow it's billedEst. monthly costEffective cost / post
1,000Within the 3,000 included$19.00$0.019
10,000$19 + 7,000 × $0.007$68.00$0.0068
100,000$19 + 7,000 × $0.007 + 90,000 × $0.005$518.00$0.0052

The more you publish, the cheaper each post gets. For a small startup that means:

  • Prototyping (≈1k posts/mo): stays inside the base plan at $19/mo — effectively free to validate an idea across every platform.
  • Scaling (≈10k posts/mo): ~$68/mo, still less than the salary-hours it takes to build and maintain a single platform integration in-house.
  • High volume (≈100k posts/mo): ~$518/mo, or about half a cent per post, all-in, across all supported networks.

The ROI case is the integration you don't build: one Outstand integration replaces eleven separate OAuth apps, SDKs, and review processes — each of which can take weeks to build and requires ongoing maintenance as platform APIs change.