OutReply Public API

A clean, scoped REST API for automating your social media presence. Schedule posts, upload media, moderate comments, and list your brands & pages from any language that speaks HTTP. Designed for Make.com, Zapier, n8n, custom cron jobs, and in-house apps.

🔐
Scoped tokens
Fine-grained permissions. Grant only what the integration needs.
📅
Scheduling
Schedule posts up to 75 days out. Cancel anytime.
💬
Comments
List and reply to comments on your published posts.
🖼️
Media uploads
Upload images/videos or reference public URLs.

Current version

This documentation covers v1. We add new fields non-breakingly and version the URL (/api/v2) before any breaking change.

Quickstart

  1. Open the API keys page in your OutReply dashboard.
  2. Click + New token, name it, pick only the scopes you need, then Create.
  3. Copy the token (starts with outreply_live_) — it is shown only once.
  4. Call any endpoint with Authorization: Bearer <your token>.
# Who am I and what can this token do?
curl https://api.outreply.com/api/v1/me \
  -H "Authorization: Bearer outreply_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Prefer a typed client?

We publish official SDKs for Node.js and Python — see Official SDKs below. One line to install, typed resources, automatic idempotency, and a built-in webhook signature verifier.

Official SDKs

Thin, typed wrappers around the same REST endpoints documented on this page. Zero runtime dependencies on Node (uses fetch), httpx on Python. Both ship with a webhook signature verifier, typed errors, and automatic idempotency keys on write requests.

🟢
Node.js — @outreply/node
TypeScript-first, ESM + CJS, Node 18+. npm · source
🐍
Python — outreply
Typed, Python 3.9+, sync client built on httpx. PyPI · source
✅
Auto idempotency
Every POST/DELETE gets an Idempotency-Key automatically — safe retries out of the box.
🔐
Webhook verifier
Constant-time HMAC verification that handles rotation windows (multiple signatures) transparently.

Install

# Node.js
npm install @outreply/node

# Python
pip install outreply

Node.js quickstart

import OutReply from "@outreply/node";

const client = new OutReply({ apiKey: process.env.OUTREPLY_API_KEY });

// Who am I?
const me = await client.me.retrieve();

// Schedule a post
const post = await client.posts.schedule({
  page_id: "6601...",
  content: "Hello from the Node SDK 👋",
  scheduled_at: new Date(Date.now() + 60 * 60 * 1000).toISOString(),
});

Python quickstart

from outreply import OutReply

client = OutReply(api_key=os.environ["OUTREPLY_API_KEY"])

me = client.me.retrieve()

post = client.posts.schedule(
    page_id="6601...",
    content="Hello from the Python SDK 👋",
    scheduled_at="2026-05-01T10:00:00Z",
)

Webhook verification

// Node: Express receiver
import { verifyWebhook } from "@outreply/node/webhooks";

app.post("/webhooks/outreply", express.raw({ type: "application/json" }), (req, res) => {
  const event = verifyWebhook({
    rawBody: req.body,
    signatureHeader: req.header("x-outreply-signature"),
    secret: process.env.OUTREPLY_WEBHOOK_SECRET,
  });
  // event is typed by event name
  res.status(200).end();
});
# Python: stdlib-friendly
from outreply.webhooks import verify_webhook

event = verify_webhook(
    raw_body=request.body,
    signature_header=request.headers["x-outreply-signature"],
    secret=os.environ["OUTREPLY_WEBHOOK_SECRET"],
)

Status: 1.0 beta

Both SDKs follow semver. We version the API URL (/api/v2) before any breaking change, so 1.x will keep working against the current API indefinitely. Bug reports and PRs welcome on outreply-node or outreply-python.

Authentication

Every request to /api/v1/* (except the root probe /api/v1/) must include an API token.

Bearer header (recommended)

Authorization: Bearer outreply_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Alternative: X-API-Key header

X-API-Key: outreply_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Tokens begin with outreply_live_ (older orply_live_ tokens still work for backward compatibility). If a token leaks, revoke or rotate it immediately from the dashboard.

Scopes

Scopes gate what a token can do. Grant the minimum set your integration needs.

ScopeGroupGrants
read:accountAccountBasic user info + quota
read:brandsBrandsList brands you collaborate on
read:pagesPagesList social pages under those brands
read:postsPostsScheduled & published posts
write:postsPostsSchedule, cancel, or reschedule posts
read:commentsCommentsList comments on published posts
write:commentsCommentsReply to comments
read:mediaMediaList your uploaded media
write:mediaMediaUpload & delete media
manage:webhooksWebhooksSubscribe to and manage webhooks
write:aiAIGenerate images, videos & captions — spends AI credits, see AI credits & top-ups
*—All of the above (discouraged)

A request without the required scope returns 403 MISSING_SCOPE. The response body lists both the required scope and your token's current scopes.

Tiers & limits

API quotas now meter the actions that actually cost us money (publishes, comment replies) separately from cheap reads. Every counter resets on the 1st of each UTC month.

LimitFreeDeveloper
$9/mo
ProElite
Publishes / month (/posts/publish + /posts/schedule)602005003 000
Comment replies / month (/comments/reply)2001 0003 00020 000
Read requests / month (GET *)10 00040 000100 0001 000 000
Webhook deliveries / month2 00015 00050 000500 000
Requests per minute (burst)103060300
Concurrent upload storage100 MB500 MB1 GB5 GB
Max file size per upload25 MB50 MB100 MB250 MB
Active keys10101025
Key expirationup to 2 yearsup to 2 yearsup to 2 yearsup to 2 years

Which tier should I pick?

Developer is an API-focused SKU priced at $9/mo — built for indie devs, hackathon projects, and small automations that outgrew the free bucket but don't need the full Advanced product surface (AI recommendations, multi-brand, etc.). It keeps the full webhook event catalogue and all API scopes, just with lower monthly caps. Upgrade to Pro (Advanced) or Elite when you need more brands, more throughput, or AI features.

Per-page publish guardrails (platform-aware, all tiers)

Monthly quotas gate our infra; these guardrails protect your connected accounts from each platform's own spam-detection. They're tuned per platform and apply on every tier:

PlatformMax / hourMax / 24h
Facebook (FB)1040
Instagram (IG)830
LinkedIn (LN)315
TikTok (TT)1050
X / Twitter (TW)50300
  • 60 s minimum gap between scheduled posts on the same page.
  • 500 publishes / brand / 24h runaway safety-net (sits well above any legitimate multi-page setup).

Page caps return 429 PAGE_PUBLISH_GUARDRAIL; the brand safety-net returns 429 BRAND_PUBLISH_GUARDRAIL. Both include retry_after_seconds.

How storage works

Uploaded files count against your concurrent storage quota until the post they're attached to publishes (then they're freed), or you delete them. Passing media_urls in /posts/schedule does not touch your storage quota — we download the asset only when it's time to publish.

Every response includes headers that show your real-time budget:

X-API-Tier: pro
X-RateLimit-Limit: 30
X-RateLimit-Remaining: 28
X-RateLimit-Reset: 1744963280
X-API-Scopes: read:pages write:posts write:media

AI credits & top-ups

Every /ai/* call (images, image edits, videos, captions) spends the account's AI credits — the same credits used in the dashboard and in the OutReply app for ChatGPT. Reads, publishes and replies never touch credits; they are metered by the monthly quotas above.

EndpointCredits
POST /ai/captions1
POST /ai/images10
POST /ai/images/edit10
POST /ai/videos (Elite plan)90

Two balances, one number

  • Plan credits renew every month with the subscription (Free 30 · Developer 100 · Advanced 1,300 · Elite 2,400).
  • Bonus credits are bought as one-time packs or added by auto-refill. They never expire and are spent only after plan credits run out.

GET /account/credits returns both balances, the cost table, the packs on sale and your auto-refill status. Every AI response also carries the X-Credits-Remaining header and a credits object so you can budget without an extra call.

Running out

When the balance cannot cover a call you get 402 INSUFFICIENT_CREDITS — nothing is charged:

{
  "error": "Not enough AI credits: this action costs 10 and 4 are available.",
  "code": "INSUFFICIENT_CREDITS",
  "required": 10,
  "available": 4,
  "plan_remaining": 0,
  "bonus": 4,
  "billing_url": "https://www.outreply.com/account/billing",
  "packs_url": "https://www.outreply.com/account/billing#packs",
  "auto_refill_url": "https://www.outreply.com/account/billing#auto-refill"
}

Power users: turn on auto-refill

If your integration generates media around the clock, enable auto-refill under Account › Credits & Billing: pick a threshold (for example 50 credits) and a refill size (500–5,000). When the balance drops under the threshold we add the credits instantly and bill them once a month, so an automation never stops on a 402. Safety caps: max 5 refills per day, one every 10 minutes. One-time credit packs (500 to 15,000 credits) are available on every plan, including Free.

Failed generations are refunded automatically (credits_charged becomes 0 on the job). Charges are idempotent when you send an Idempotency-Key.

Security controls

🔒 Scopes
Per-token permissions. Drop tokens into single-purpose integrations.
⏳ Expiration
Expire tokens after 7 / 30 / 90 / 180 days or any custom value up to 2 years.
🌍 IP allowlist
Restrict a token to specific IPv4 addresses or CIDR ranges.
🔁 Rotation
Rotate a token without changing its name/scopes. Previous secret invalidated immediately.
🏷 Brand restriction
Scope a token to specific brands so it can't reach others under your account.
🛡 Upload hardening
Magic-byte MIME verification, strict extension allowlist, UUID filenames, SVG rejected.
🧱 SSRF protection
media_urls validated to reject localhost, private ranges, and non-http(s) schemes.
📜 Audit log
Every authenticated request logged (method, path, status, IP, duration, scope denials) for 30 days.

Sandbox tokens

Every new OutReply account is issued a sandbox API token automatically — no credit card, no paid plan. Sandbox tokens let you try the API before committing.

  • Prefix: outreply_test_…
  • Lifetime cap: 100 requests total
  • Rate: 5 / minute, 50 / day
  • Scopes: read-only (read:account, read:brands, read:pages, read:posts, read:comments, read:media)
  • Returned once in the signup response as sandbox_api_token, then shown in the dashboard.

When the lifetime cap is reached the API returns 402 SANDBOX_EXHAUSTED with an upgrade_url pointing at the dashboard. Sandbox requests do not count against your paid quota and don't trigger webhooks.

Collaborator-issued tokens

OutReply is brand-native: every integration is owned by a brand, and collaborators can issue their own API keys for brands they help manage.

  • Only collaborators with Full access can create keys — this keeps scopes aligned with collaborator permissions and prevents privilege mismatches.
  • A collaborator-issued key is hard-bound to one brand (on_behalf_of_brand_id) and stamped with issued_by_collaborator_id.
  • Every create / revoke / rotate is written to the brand's audit log, so owners always have a timeline of who did what.
  • Automatic revoke: when a collaborator is removed from the brand, every API key they issued is immediately revoked. Requests then fail with 401 TOKEN_REVOKED.
  • Permission downgrade: if a collaborator loses full_control later, their live keys stop working with 403 COLLABORATOR_INSUFFICIENT.

Idempotency

All POST, PATCH and DELETE endpoints accept an Idempotency-Key request header (Stripe-compatible). If your request fails mid-flight, you can safely retry with the same key — we return the original response instead of performing the action twice.

curl https://api.outreply.com/api/v1/posts/schedule \
  -H "Authorization: Bearer outreply_live_..." \
  -H "Idempotency-Key: 8f1c1d7a-2a99-4c12-9fcc-6b5d22e52a7b" \
  -H "Content-Type: application/json" \
  -d '{"page_id":"...","message":"Launch!","publish_at":"2026-05-01T10:00:00Z"}'
  • Keys are scoped per API token and stored for 24 hours.
  • Replays return the cached body with header Idempotency-Replayed: true.
  • Same key + different request body → 409 IDEMPOTENCY_KEY_MISMATCH.
  • Binary multipart uploads are skipped automatically — use a distinct key per upload.
  • Keys must be 1–120 characters. We recommend UUIDv4.

Webhooks

Subscribe a URL to events and receive signed HTTP callbacks the moment they happen — no polling required. Manage subscriptions in Dashboard → API → Webhooks.

Events

EventFires when
post.publishedA scheduled post is successfully published on the platform.
post.failedAll retry attempts for a scheduled post were exhausted.
comment.receivedSomeone comments on one of your linked pages.
comment.repliedA reply was posted through the API.
account.quota.warningYour account crossed 80% of a monthly action bucket (publishes / replies / reads / webhooks). Fires once per bucket per month.
ai.job.completedAn asynchronous AI job (POST /ai/videos) finished. data.job is the job, data.media.media_id is ready for /posts/schedule.
ai.job.failedAn asynchronous AI job failed. Credits were refunded; data.job.error says why.

Payload shape

{
  "event": "post.published",
  "created_at": "2026-04-12T10:14:22.917Z",
  "data": {
    "userId": "65f...",
    "brandId": "65f...",
    "post": { /* scheduled post object */ }
  }
}

Signature verification

Every request carries X-OutReply-Signature: sha256=<hex>. Verify with HMAC-SHA256 over the raw body using your webhook secret. Or skip the boilerplate and use verifyWebhook from @outreply/node/webhooks / verify_webhook from outreply.webhooks — both handle rotation-window multi-signatures correctly.

const crypto = require("crypto");
function verify(rawBody, header, secret) {
  const expected = crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
  // header may contain multiple signatures during a secret rotation window
  return header.split(",").some(s => s.trim() === `sha256=${expected}`);
}

Retries & auto-disable

  • Any non-2xx response (or timeout > 10s) is retried up to 5 times with exponential backoff: 30s → 2m → 10m → 30m → 2h.
  • After 20 consecutive failures the webhook is auto-disabled — re-enable it from the dashboard once the receiver is healthy.
  • Every delivery is logged for 30 days; inspect them from the dashboard or via the API.

Secret rotation

Click Rotate secret on a webhook to generate a new signing key. The previous secret stays valid for 24 hours so your receivers can deploy the new value without downtime. During that window each delivery's signature header lists both signatures separated by a comma — your verifier only needs to accept whichever matches.

Errors

All errors return JSON with an error string and a stable machine-readable code. Example:

{
  "error": "This API token is missing the required scope: write:posts",
  "code": "MISSING_SCOPE",
  "required_scope": "write:posts",
  "your_scopes": ["read:posts"]
}
HTTPCodeMeaning
400MISSING_FIELDRequired field missing from request body
400INVALID_DATEMalformed timestamp
400INVALID_MEDIA_URLmedia_urls contains localhost, private IP, or non-http(s) URL
400INVALID_FILE_CONTENTUploaded file's magic bytes don't match declared MIME
400UPLOAD_ERRORMalformed multipart, unsupported extension, etc.
401MISSING_TOKENNo token in request
401INVALID_TOKENToken is malformed or not found
401TOKEN_REVOKEDToken was revoked
401TOKEN_EXPIREDToken past its expiry date
403MISSING_SCOPEToken lacks required scope
403IP_NOT_ALLOWEDRequest IP not in token's allowlist
403BRAND_NOT_AUTHORIZEDToken can't reach the requested brand
403COLLABORATOR_INSUFFICIENTThe issuing collaborator no longer has full access
404NOT_FOUNDResource does not exist or not yours
409MEDIA_IN_USEMedia is attached to a pending post
413FILE_TOO_LARGEUpload exceeds per-file limit
413QUOTA_EXCEEDEDUpload would exceed concurrent storage quota
429RATE_LIMITEDToo many requests per minute — retry after Retry-After seconds
429DAILY_LIMIT_EXCEEDEDDaily request budget consumed
402SANDBOX_EXHAUSTEDSandbox token hit its 100-request lifetime cap — see response upgrade_url
400INVALID_IDEMPOTENCY_KEYIdempotency-Key is missing or > 120 chars
409IDEMPOTENCY_KEY_MISMATCHSame Idempotency-Key used with a different request body
402INSUFFICIENT_CREDITSNot enough AI credits for this /ai/* call — body has required, available, billing_url
403PLAN_REQUIREDAI feature needs a higher plan (video generation and the pro image model are Elite-only)
400INVALID_REFERENCE_MEDIAReference image URL is unreachable / not an image, or media_id is not an image you own
502AI_GENERATION_FAILEDThe AI provider failed; credits were refunded automatically
500SERVER_ERRORSomething went wrong on our side

Machine-readable catalog

The full up-to-date list of error codes — with category, HTTP status, message, and recommended fix — is available as JSON at GET /api/v1/errors (no authentication required).

Endpoint reference

GET/accountread:account

Returns the authenticated user, the current API key's metadata, and live quota usage.

Response 200

{
  "user": { "id": "6501...", "email": "you@example.com", "firstname": "Alex", "lastname": "Doe" },
  "credits": { "remaining": 420, "plan": "pro" },
  "tier": "pro",
  "api_key": {
    "id": "6601...",
    "name": "Zapier integration",
    "prefix": "outreply_live_ab12c",
    "scopes": ["read:account", "write:posts"],
    "brand_ids": [],
    "expires_at": null,
    "last_used_at": "2026-04-15T14:22:08.000Z",
    "request_count": 142
  },
  "quota": {
    "storage": { "used_bytes": 2345678, "limit_bytes": 1073741824, "remaining_bytes": 1071396146, "max_file_bytes": 104857600 },
    "rate_limit": { "per_minute": 60, "per_day": 90000 },
    "monthly": {
      "year_month": "2026-05",
      "resets_at": "2026-06-01T00:00:00.000Z",
      "publishes": { "used": 84,  "limit": 500,    "remaining": 416 },
      "replies":   { "used": 312, "limit": 3000,   "remaining": 2688 },
      "reads":     { "used": 5421,"limit": 100000, "remaining": 94579 },
      "webhooks":  { "used": 41,  "limit": 50000,  "remaining": 49959 },
      "writes":    { "used": 17,  "limit": null,   "remaining": null },
      "total_requests": 5875
    },
    "publish_guardrails": {
      "perPage": {
        "FB": { "perHour": 10, "per24h": 40  },
        "IG": { "perHour": 8,  "per24h": 30  },
        "LN": { "perHour": 3,  "per24h": 15  },
        "TT": { "perHour": 10, "per24h": 50  },
        "TW": { "perHour": 50, "per24h": 300 }
      },
      "minGapBetweenSchedulesMs": 60000,
      "perBrandPer24hSafetyNet": 500
    }
  }
}
GET/me(none — authenticated only)

Lightweight introspection. Use for health checks or "whoami" probes — any valid token works regardless of scopes.

Response 200

{
  "authenticated_as": { "user_id": "...", "email": "you@example.com", "firstname": "Alex", "lastname": "Doe" },
  "api_key": { "id": "...", "name": "Zapier", "prefix": "outreply_live_ab12c", "scopes": ["*"], "expires_at": null }
}
GET/brandsread:brands

Lists the brands you collaborate on. If the token was restricted to specific brands at creation time, only those are returned.

Response 200

{
  "brands": [
    {
      "id": "65f...",
      "name": "Acme Co.",
      "logo": "https://.../logo.png",
      "timezone": "Europe/Paris",
      "created_at": "2025-01-08T10:20:30.000Z",
      "pages_count": 4
    }
  ]
}
GET/brands/:brandIdread:brands

Details for a single brand, including its linked pages.

Response 200

{
  "brand": {
    "id": "65f...",
    "name": "Acme Co.",
    "logo": "https://.../logo.png",
    "timezone": "Europe/Paris",
    "pages": [ /* same as /pages response */ ]
  }
}
GET/brands/:brandId/pagesread:pages

All pages linked to a brand. Identical shape to /pages filtered by brand.

GET/pagesread:pages

Every social page across every brand you can reach, flattened for easy iteration.

Query parameters

NameTypeDescription
platform optionalstringFilter by facebook, instagram, linkedin, twitter, tiktok, youtube
brand_id optionalstringLimit to a specific brand

Response 200

{
  "pages": [
    {
      "id": "6711...",
      "brand_id": "65f...",
      "brand_name": "Acme Co.",
      "platform": "instagram",
      "name": "Acme Official",
      "username": "acme",
      "followers": 12450,
      "expired": false
    }
  ]
}
GET/pages/:pageIdread:pages

Details for a single page.

Response 200

{
  "page": {
    "id": "6711...",
    "brand_id": "65f...",
    "platform": "instagram",
    "name": "Acme Official",
    "username": "acme",
    "followers": 12450,
    "profile_picture": "https://...",
    "expired": false,
    "created_at": "2024-12-01T..."
  }
}
POST/posts/schedulewrite:posts

Schedule a post on one of your pages. Must be at least 10 minutes and at most 75 days in the future.

Body

FieldTypeDescription
page_id requiredstringTarget page ID from /pages
scheduled_at requiredISO-8601UTC timestamp. E.g. 2026-05-12T14:30:00Z
message optional*stringPost text. Max 5 000 chars. *Either message or one media item is required.
media_ids optionalstring[]IDs of previously uploaded media (see /media/upload)
media_urls optionalstring[]Public https URLs. Validated against SSRF (no localhost, private IPs, etc.)
first_comment optionalstringPosted as the first comment right after publish (Instagram, Facebook)
tiktok_settings optionalobjectTikTok-specific options (privacy, duet/stitch, commercial content, etc.)

Example request

curl https://api.outreply.com/api/v1/posts/schedule \
  -X POST \
  -H "Authorization: Bearer outreply_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "page_id": "6711...",
    "message": "Launching tomorrow 🚀",
    "scheduled_at": "2026-05-01T09:00:00Z",
    "media_urls": ["https://cdn.example.com/hero.jpg"],
    "first_comment": "More details at acme.com"
  }'

Response 201

{
  "scheduled_post": {
    "id": "68a1...",
    "page_id": "6711...",
    "platform": "instagram",
    "scheduled_at": "2026-05-01T09:00:00.000Z",
    "status": "scheduled",
    "message": "Launching tomorrow 🚀",
    "media": [{ "src": "https://cdn.example.com/hero.jpg", "type": "photo" }],
    "first_comment": "More details at acme.com",
    "created_at": "2026-04-18T12:00:00.000Z"
  }
}
POST/posts/publishwrite:posts

Publish a post immediately on one of your pages. Same body as /posts/schedule minus scheduled_at. The HTTP response is only returned once the upstream platform has accepted the post, so expect multi-second latency for video uploads.

Body

FieldTypeDescription
page_id requiredstringTarget page ID from /pages
message optional*stringPost text. Max 5 000 chars. *Either message or one media item is required.
media_ids optionalstring[]IDs from /media/upload
media_urls optionalstring[]Public https URLs (SSRF-validated)
first_comment optionalstringPosted as the first comment right after publish (Instagram, Facebook)
tiktok_settings optionalobjectTikTok-specific options

Example request

curl https://api.outreply.com/api/v1/posts/publish \
  -X POST \
  -H "Authorization: Bearer outreply_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "page_id": "6711...",
    "message": "Live from the API 🚀",
    "media_urls": ["https://cdn.example.com/hero.jpg"],
    "first_comment": "More details at acme.com"
  }'

Response 201

{
  "post_id": "17841...",
  "platform": "IG",
  "published_at": "2026-05-01T09:00:00.000Z",
  "message": "Live from the API 🚀",
  "permalink": "https://instagram.com/p/...",
  "status": "published"
}
GET/posts/scheduledread:posts

Lists pending scheduled posts.

Query parameters

page_id optionalFilter by page
limit optional1–100, default 50

Response 200

{
  "scheduled_posts": [
    {
      "id": "68a1...",
      "page_id": "6711...",
      "platform": "instagram",
      "scheduled_at": "2026-05-01T09:00:00.000Z",
      "status": "scheduled",
      "message": "Launching tomorrow 🚀"
    }
  ]
}
GET/posts/scheduled/:postIdread:posts

Details of a single scheduled post.

Response 200

{ "scheduled_post": { /* same shape as create response */ } }
DELETE/posts/scheduled/:postIdwrite:posts

Cancels a pending post. Returns 404 if already cancelled or published.

Response 200

{ "success": true, "id": "68a1..." }
GET/posts/publishedread:posts

Lists posts that have been published through OutReply.

Query parameters

page_idFilter by page
limit1–100, default 50
sinceISO-8601; only posts published after this time

Response 200

{
  "published_posts": [
    {
      "id": "68a1...",
      "page_id": "6711...",
      "platform": "instagram",
      "remote_id": "17891234567890123",
      "permalink": "https://www.instagram.com/p/C9x.../",
      "published_at": "2026-05-01T09:00:15.000Z",
      "message": "Launching tomorrow 🚀",
      "metrics": { "likes": 42, "comments": 3, "shares": 1 }
    }
  ]
}
GET/posts/published/:id/engagementread:posts

Live engagement counts for a post that was published through OutReply. Pass the id from GET /posts/published. Supported for Facebook and Instagram; other platforms return 400 UNSUPPORTED_PLATFORM (use GET /comments and the comment.received webhook there).

Response 200

{
  "id": "66a0...",
  "platform": "IG",
  "platform_post_id": "17895695668004550",
  "page_id": "65f...",
  "brand_id": "65e...",
  "engagement": {
    "likes": 124,
    "comments": 9,
    "shares": null,
    "total_interactions": 133,
    "permalink": "https://www.instagram.com/p/...",
    "published_at": "2026-09-22T09:00:12+0000"
  },
  "fetched_at": "2026-09-22T10:30:00.000Z"
}

Counts come straight from the platform on every call (a read request). Poll hourly or daily; use comment.received for real-time reactions.

GET/commentsread:comments

Lists comments on one of your published posts.

Query parameters

post_id requiredPublished post ID
limit1–100, default 50
sinceISO-8601 — only comments created after this time

Response 200

{
  "comments": [
    {
      "id": "c_abc123",
      "post_id": "68a1...",
      "platform": "instagram",
      "author": { "name": "Jane Doe", "username": "jane.doe", "profile_picture": "https://..." },
      "message": "Love this!",
      "created_at": "2026-05-01T09:15:00.000Z",
      "replies_count": 0,
      "parent_id": null
    }
  ]
}
POST/comments/replywrite:comments

Posts a reply to a comment.

Body

comment_id requiredstring
message requiredstring, max 5 000 chars

Response 201

{
  "reply": {
    "id": "c_xyz789",
    "parent_id": "c_abc123",
    "message": "Thanks 💚",
    "created_at": "2026-05-01T09:16:30.000Z"
  }
}
POST/media/uploadwrite:media

Uploads a single image or video. The field name must be file (multipart/form-data).

Security & limits

  • Allowed MIME types: image/jpeg, image/png, image/gif, image/webp, video/mp4, video/quicktime, video/webm
  • SVG is rejected (XSS vector). HTML/PDF/ZIP/SWF are rejected.
  • Magic-byte verification: if the file's bytes don't match its declared MIME, the upload is refused.
  • Max per-file size: 25 MB (free) / 100 MB (pro).
  • Concurrent storage: 100 MB (free) / 500 MB (developer) / 1 GB (pro) / 5 GB (elite).

Example request

curl https://api.outreply.com/api/v1/media/upload \
  -H "Authorization: Bearer outreply_live_..." \
  -F "file=@./hero.jpg"

Response 201

{
  "media_id": "69b2...",
  "filename": "api_d8f3c...a1.jpg",
  "size_bytes": 2345678,
  "type": "photo",
  "mime_type": "image/jpeg",
  "url": "https://api.outreply.com/assets/media/posts/uploads/api_d8f3c...a1.jpg",
  "tier": "pro",
  "quota": { "used_bytes": 8000000, "limit_bytes": 1073741824, "remaining_bytes": 1065741824 }
}

Error responses

400 UPLOAD_ERRORBad multipart, disallowed extension, or disallowed MIME
400 INVALID_FILE_CONTENTMagic bytes don't match declared MIME (spoofed file)
400 NO_FILEField file missing
413 FILE_TOO_LARGESingle file exceeds your tier's max
413 QUOTA_EXCEEDEDUpload would push you over your concurrent storage quota
GET/mediaread:media

Lists your active (non-deleted) uploaded media.

Response 200

{
  "media": [
    {
      "media_id": "69b2...",
      "filename": "api_d8f3c...a1.jpg",
      "size_bytes": 2345678,
      "type": "photo",
      "mime_type": "image/jpeg",
      "scheduled_post_id": null,
      "created_at": "2026-04-18T12:00:00.000Z"
    }
  ],
  "tier": "pro",
  "quota": { "used_bytes": 8000000, "limit_bytes": 1073741824, "remaining_bytes": 1065741824 }
}
DELETE/media/:mediaIdwrite:media

Deletes a media item. Fails with 409 MEDIA_IN_USE if the file is attached to a pending scheduled post — cancel the post first.

Response 200

{ "success": true, "media_id": "69b2..." }

AI generation & credits

Generate images, edit them, render short videos and write captions in your brand's voice. Every call needs the write:ai scope and spends AI credits; generated files are registered as media so you can attach them to posts with media_ids.

GET/account/creditsread:account

Credit balance, per-action costs, credit packs on sale and auto-refill status. Call it before a batch of AI work, or react to 402 INSUFFICIENT_CREDITS.

Response 200

{
  "credits": {
    "available": 1840,
    "plan": "promonthly",
    "plan_allowance": 2400,
    "plan_used": 1060,
    "plan_remaining": 1340,
    "bonus": 500,
    "referral": 0,
    "next_reset_at": "2026-10-01T00:00:00.000Z"
  },
  "costs": [
    { "action": "postText", "label": "AI post text / caption", "credits": 1 },
    { "action": "imageGenerate", "label": "Image (Nano Banana)", "credits": 10 },
    { "action": "videoGenerate", "label": "Video (Veo, Elite plan)", "credits": 90 }
  ],
  "packs": [
    { "id": "boost", "name": "Boost", "credits": 1500, "priceUsd": 12, "available": true }
  ],
  "auto_refill": { "available": true, "enabled": true, "status": "active", "threshold": 50, "amount": 1000, "unitCredits": 100, "unitPriceUsd": 1 },
  "billing_url": "https://www.outreply.com/account/billing"
}
POST/ai/imageswrite:ai · 10 credits

Generates one image with OutReply's AI image model. The file is stored and registered as media, so the returned media_id can go straight into /posts/schedule or /posts/publish. Synchronous — typically 10–30 seconds.

Body

prompt requiredWhat the image should show: subject, style, mood, colors, any text (max 2,000 chars)
aspect_ratiodefault · 1:1 (feed) · 16:9 · 9:16 (stories / reels) · 4:3 · 3:4
modelflash (default) or pro (higher quality, Elite plan only → 403 PLAN_REQUIRED otherwise)
reference_image_urlsUp to 3 public image URLs used as style / subject references
reference_media_idsUp to 3 media_ids of images you uploaded or generated
brand_idApplies that brand's Images guidelines from the dashboard configuration

Example request

curl https://api.outreply.com/api/v1/ai/images \
  -H "Authorization: Bearer outreply_live_..." \
  -H "Idempotency-Key: 4b0c...e1" \
  -H "Content-Type: application/json" \
  -d '{ "prompt": "Flat-lay of a summer skincare set on pastel background, soft light", "aspect_ratio": "1:1", "brand_id": "65f..." }'

Response 201

{
  "job": {
    "job_id": "66f1...", "kind": "image", "status": "completed",
    "url": "https://api.outreply.com/assets/media/posts/uploads/api_ai_1c9e...png",
    "media_id": "66f2...", "media_type": "photo", "credits_charged": 10,
    "params": { "aspect_ratio": "1:1", "model": "gemini-2.5-flash-image" }
  },
  "media": { "media_id": "66f2...", "type": "photo", "mime_type": "image/png", "size_bytes": 812345, "url": "https://api.outreply.com/assets/media/posts/uploads/api_ai_1c9e...png" },
  "credits": { "charged": 10, "remaining": 1830, "plan_remaining": 1330, "bonus": 500, "plan": "promonthly" }
}

Error responses

400 INVALID_REFERENCE_MEDIAReference URL unreachable / not an image, or media_id is not an image you own
402 INSUFFICIENT_CREDITSSee AI credits & top-ups — nothing charged
403 PLAN_REQUIREDmodel: "pro" needs the Elite plan
413 QUOTA_EXCEEDEDStoring the file would exceed your storage quota — delete old media first
502 AI_GENERATION_FAILEDProvider error; credits refunded
POST/ai/images/editwrite:ai · 10 credits

Edits an existing image with a natural-language instruction ("remove the text", "make it night time", "add our logo top-left"). Pass either image_url (public URL) or media_id (uploaded or generated). Returns the same shape as /ai/images.

curl https://api.outreply.com/api/v1/ai/images/edit \
  -H "Authorization: Bearer outreply_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "media_id": "66f2...", "prompt": "Replace the background with a clean white studio backdrop" }'
POST/ai/videoswrite:ai · 90 credits · Elite

Starts a short video generation (text-to-video, or image-to-video when image_url / media_id is set). Rendering takes 1–3 minutes, so the call returns 202 with a processing job. Poll GET /ai/jobs/:id or subscribe to the ai.job.completed / ai.job.failed webhooks. Credits are reserved up front and refunded if the job fails.

Body

prompt requiredScene, motion, camera and mood (max 2,000 chars)
aspect_ratiodefault · 16:9 · 9:16 (reels / TikTok)
image_url / media_idOptional image to animate
brand_idApplies that brand's Video guidelines

Response 202

{
  "job": { "job_id": "66f3...", "kind": "video", "status": "processing", "url": null, "media_id": null, "credits_charged": 90 },
  "poll_url": "/api/v1/ai/jobs/66f3...",
  "credits": { "charged": 90, "remaining": 1740, "plan_remaining": 1240, "bonus": 500, "plan": "promonthly" }
}

Not on Elite? The call returns 403 PLAN_REQUIRED with an upgrade_url and charges nothing.

GET/ai/jobswrite:ai

Lists your API-created AI jobs, newest first. Query: limit (max 50) and kind (image · image_edit · video).

{ "jobs": [ { "job_id": "66f3...", "kind": "video", "status": "completed", "url": "https://api.outreply.com/assets/media/posts/uploads/api_ai_...mp4", "media_id": "66f4...", "credits_charged": 90 } ] }
GET/ai/jobs/:idwrite:ai

Returns one job and, once completed, its registered media. Poll every 5–10 seconds while status is processing; a failed job carries error and credits_charged: 0 (refunded).

{
  "job": { "job_id": "66f3...", "status": "completed", "url": "https://api.outreply.com/assets/media/posts/uploads/api_ai_9a1...mp4", "media_id": "66f4...", "completed_at": "2026-09-22T10:14:02.000Z" },
  "media": { "media_id": "66f4...", "type": "video", "mime_type": "video/mp4", "size_bytes": 7340032, "url": "https://api.outreply.com/assets/media/posts/uploads/api_ai_9a1...mp4" }
}
POST/ai/captionswrite:ai · 1 credit

Writes post text in the brand's configured voice (the Posts guidelines from the dashboard). Combine with /ai/images and /posts/schedule for a fully generated post in three calls.

Body

brand_id requiredBrand whose voice and guidelines to use
instruction requiredWhat the post is about, e.g. "Announce our summer sale, 20% off skincare, ends Sunday"
wordsTarget length, 10–300 (default 60)
edit_textExisting text to rewrite instead of writing from scratch
custom_instructionsStrict instructions that override the brand configuration (tone, emojis, hashtags…)

Response 200

{
  "text": "Summer glow, minus the price tag ☀️ 20% off every skincare set until Sunday…",
  "brand_id": "65f...",
  "words_requested": 60,
  "credits": { "charged": 1, "remaining": 1739, "plan_remaining": 1239, "bonus": 500, "plan": "promonthly" }
}

Integrations

The API works everywhere. Here are ready-to-copy blueprints for the most common no-code tools.

🤖 Fully automated content pipeline (caption → video → publish → monitor)

Everything below runs on one outreply_live_* token with the scopes read:brands, write:ai, write:posts, read:posts, read:comments. Costs: 1 credit for the caption, 90 for the video (Elite plan).

# 1. Write the caption in the brand voice (1 credit)
curl https://api.outreply.com/api/v1/ai/captions -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{ "brand_id": "$BRAND", "instruction": "Announce our new lavender candle, cozy autumn vibes", "words": 50 }'
# → { "text": "…", "credits": { "remaining": 2399 } }

# 2. Start the video (90 credits, returns 202 immediately)
curl https://api.outreply.com/api/v1/ai/videos -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{ "brand_id": "$BRAND", "prompt": "Slow pan over a lavender candle flickering on a wooden table, warm autumn light", "aspect_ratio": "9:16" }'
# → { "job": { "job_id": "66f3…", "status": "processing" }, "poll_url": "/api/v1/ai/jobs/66f3…" }

# 3. Wait for it — poll every 10 s, or subscribe a webhook to ai.job.completed
curl https://api.outreply.com/api/v1/ai/jobs/66f3… -H "Authorization: Bearer $TOKEN"
# → { "job": { "status": "completed", "url": "https://api.outreply.com/…/api_ai_….mp4", "media_id": "66f4…" } }

# 4. Schedule (or /posts/publish for now) with the generated video
curl https://api.outreply.com/api/v1/posts/schedule -H "Authorization: Bearer $TOKEN" -H "Idempotency-Key: $(uuidgen)" -H "Content-Type: application/json" \
  -d '{ "page_id": "$PAGE", "message": "", "scheduled_at": "2026-10-01T09:00:00Z", "media_ids": ["66f4…"] }'
# → post.published webhook fires when it goes live

# 5. Monitor engagement
curl "https://api.outreply.com/api/v1/posts/published?platform=IG&limit=20" -H "Authorization: Bearer $TOKEN"
curl https://api.outreply.com/api/v1/posts/published/66a0…/engagement -H "Authorization: Bearer $TOKEN"
curl "https://api.outreply.com/api/v1/comments?page_id=$PAGE&post_id=17895…" -H "Authorization: Bearer $TOKEN"

Keep in mind

  • Generated media that is not attached to a post within 24 hours is cleaned up like an abandoned upload. Schedule it, or download the url, before then.
  • Instagram and TikTok require media; Instagram reels want 9:16.
  • Video generation needs the Elite plan; captions and images work on every plan. Budget credits with GET /account/credits and turn on auto-refill for unattended pipelines.
🔗 Make.com (Integromat)

Use the built-in HTTP > Make a request module. No custom connector needed.

  1. Add HTTP > Make a request.
  2. URL: https://api.outreply.com/api/v1/posts/schedule
  3. Method: POST
  4. Headers: Authorization: Bearer {{connection.outreply_token}}, Content-Type: application/json
  5. Body type: Raw / JSON. Map fields from upstream modules:
{
  "page_id": "{{1.page_id}}",
  "message": "{{1.caption}}",
  "scheduled_at": "{{formatDate(1.publish_time; YYYY-MM-DDTHH:mm:ssZ)}}",
  "media_urls": ["{{1.image_url}}"]
}

Tip

Create a Make Connection of type Custom token so the token isn't pasted into every scenario.

⚡ Zapier

Use the Webhooks by Zapier built-in action.

  1. Action: Webhooks by Zapier > Custom Request.
  2. Method: POST.
  3. URL: https://api.outreply.com/api/v1/posts/schedule.
  4. Data pass-through: No. Data: paste raw JSON and tick "JSON":
{
  "page_id": "YOUR_PAGE_ID",
  "message": "{{step1.caption}}",
  "scheduled_at": "{{step1.iso_time}}",
  "media_urls": ["{{step1.image_url}}"]
}

Headers

AuthorizationBearer outreply_live_...
Content-Typeapplication/json

For scheduled comment replies, use Schedule by Zapier as the trigger, then call POST /comments/reply with the comment_id from a previous step.

🧩 n8n

Use the built-in HTTP Request node with Header Auth credentials.

  1. Create credential: Header Auth with name Authorization and value Bearer outreply_live_....
  2. Add an HTTP Request node: URL https://api.outreply.com/api/v1/posts/schedule, Method POST, Body type JSON.
  3. Combine with a Schedule Trigger + Set node to template the payload.
🖥️ cURL + cron (bare minimum)

A one-line crontab entry that schedules tomorrow's 09:00 post with a rotating tip:

# /etc/crontab — 09:00 every day
0 9 * * * curl -sS https://api.outreply.com/api/v1/posts/schedule \
  -H "Authorization: Bearer $OUTREPLY_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{
    \"page_id\": \"$PAGE_ID\",
    \"message\": \"$(shuf -n1 /opt/tips.txt)\",
    \"scheduled_at\": \"$(date -u -d 'tomorrow 09:00' +%Y-%m-%dT%H:%M:%SZ)\"
  }"

Coming soon

We ship incrementally. Here's what's on deck — watch this page or subscribe to the changelog.

📡 Webhooks Shipped
Signed HMAC-SHA256 HTTP callbacks for post.published, post.failed, comment.received, comment.replied, account.quota.warning. Retries + auto-disable + secret rotation. See the Webhooks section.
📊 Analytics endpoints Q3
Time-series reach, engagement, and growth metrics per page.
🤖 AI generation Shipped
Images (/ai/images), edits, videos (/ai/videos) and captions (/ai/captions) that feed straight into /posts/schedule via media_id. Metered by AI credits with packs and auto-refill.
📦 Bulk scheduling Q3
Submit 50 posts in one request with per-item error reporting.
🧰 Official SDKs Q4
Typed Node, Python, and Go clients with retries, pagination helpers, and webhook verifiers baked in.
📝 OpenAPI 3.1 spec Q4
Import into Postman, Insomnia, or generate your own client.
🔑 OAuth app tokens Later
Publish integrations other OutReply users can install with one click — without sharing a static token.
🌐 GraphQL gateway Later
A companion endpoint for apps that prefer a single typed query.

Have a feature you need?

Email api@outreply.com — the roadmap is actively shaped by integrators.