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:
| Platform | network value | Connect via |
|---|---|---|
| X (Twitter) | x | OAuth |
linkedin | OAuth | |
instagram | OAuth | |
facebook | OAuth | |
| Threads | threads | OAuth |
| TikTok | tiktok | OAuth |
| YouTube | youtube | OAuth |
pinterest | OAuth | |
| Google Business Profile | google_business | OAuth |
| Vimeo | vimeo | OAuth |
| Bluesky | bluesky | App 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:
| Milestone | Typical effort |
|---|---|
| Sign up, generate an API key, first authenticated call | ~5 minutes |
| Connect your first social account via OAuth | ~30 minutes |
| Publish your first post | under 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/callbackReplace {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=brandStore 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:
- Authenticate every request with your API key (
Authorization: Bearer …). - 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. - Poll the posts list to see what is coming up or has already gone out.
- Interpret outcomes by reading each post's per-account
status(pending,published,failed,deleted) and theerror/platformPostIdfields.
Scheduling rules:
scheduledAtis 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:
| Status | Meaning |
|---|---|
pending | Created/scheduled, awaiting publish. This is your "queued" state. |
published | Delivered successfully — platformPostId holds the native post ID. |
failed | Publish failed — read error for the reason (e.g. expired token). |
deleted | The 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 posts | How it's billed | Est. monthly cost | Effective cost / post |
|---|---|---|---|
| 1,000 | Within 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.