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
*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.

LimitFreeProElite
Publishes / month (/posts/publish + /posts/schedule)605003 000
Comment replies / month (/comments/reply)2003 00020 000
Read requests / month (GET *)10 000100 0001 000 000
Webhook deliveries / month2 00050 000500 000
Requests per minute (burst)1060300
Concurrent upload storage100 MB1 GB5 GB
Max file size per upload25 MB100 MB250 MB
Active keys101025
Key expirationup to 2 yearsup to 2 yearsup to 2 years

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

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.

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
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/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) / 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..." }

Integrations

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

🔗 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 composition Q3
One-call content + image generation that feeds straight into /posts/schedule.
📦 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.