Everything the application can do, for programs: brands, scheduled posts, media, approvals, the inbox, campaigns, SmartLinks, analytics, paid media, report share links, team rosters and webhooks. The machine-readable contract is the OpenAPI 3.1 document at
/api/v1/openapi.json.
Authentication
Create a token in Settings → API access. It is shown once. Send it as a bearer token on every request:
A token acts as the person who minted it, inside the workspace it was minted in, with that person's brand roles. It stops working when it is revoked, when it expires, when its holder leaves the workspace, or when the workspace's plan no longer includes API access — each refusal says which.
Scopes
A token is minted with scopes and every endpoint declares the one it needs. A call outside the token's scopes is refused with 403 and the code insufficient_scope.
Scope
Unlocks
brands:read
List and read the brands the token holder belongs to.
brands:write
Create and update brands.
posts:read
List and read scheduled posts.
posts:write
Create, update, cancel and publish scheduled posts.
media:read
List and read media library assets.
media:write
Upload media library assets.
approvals:read
List posts awaiting review.
approvals:write
Approve, reject and request changes on posts.
inbox:read
List and read inbox threads.
inbox:write
Reply to, resolve and assign inbox threads.
campaigns:read
List content campaigns.
campaigns:write
Edit content campaigns: name, description and dates.
smartlinks:read
List SmartLinks and read their statistics.
analytics:read
Read analytics reports, daily series and post tables.
ads:read
Read paid-media accounts, campaigns and daily metrics.
reports:read
List report share links.
reports:write
Create and revoke report share links.
team:read
Read a brand's team roster.
webhooks:manage
Manage webhook subscriptions and deliveries.
Brand permissions
Scopes say what a token may attempt; the holder's brand role says what they may do. The API asks the same questions the screens do: analytics needs analytics_view, the inbox needs inbox_view to read and inbox_edit to reply, scheduling needs schedule_and_publish, approvals need review_posts, brand settings need brand_manage. A refusal is 403 with the code forbidden and the permission named in the message. A brand the holder is not a member of answers 404.
Pages and cursors
List endpoints answer with a data array and a meta object. Follow meta.next until it is null. per_page defaults to 25 and is capped at 100. Cursors are opaque; do not build them.
Send an Idempotency-Key header (any unique string, up to 128 characters) on a write. A retry with the same key within 24 hours returns the stored response with Idempotent-Replayed: true instead of running again. The same key with a different body answers 422 idempotency_key_reused; a key whose first request is still running answers 409 idempotency_in_progress.
Errors
Every error, whatever the Accept header, is JSON in one shape. Branch on code; message is for people.
The role, the token or the plan does not allow it.
404
not_found
No such resource, or one you are not a member of.
409
*_already_decided, post_not_editable, …
The resource is not in a state that allows this; details says which.
422
validation_failed
details.fields names each offending field. Never a redirect.
429
rate_limited
Retry after the seconds in Retry-After.
502
provider_rejected
The social network refused the request; the message is what it said.
Rate limits
120 requests a minute per source address, before authentication; 300 a minute per token, after it. Both answer 429 with Retry-After and the usual X-RateLimit-* headers. One busy integration cannot starve another token on the same workspace.
Endpoints
Request and response bodies for each are in the OpenAPI document. Scheduled posts are addressed as {network}/{id} because each network keeps its own table.
Register a public HTTPS endpoint with POST /api/v1/webhooks (workspace owners and admins; scope webhooks:manage). The signing secret is returned once. Every delivery is a JSON POST carrying id, event, sent_at, attempt and data, with these headers: X-Signature, X-Social-Event, X-Webhook-Id, X-Webhook-Attempt.
Events
post.scheduled
A post was added to the schedule (any network).
post.published
A scheduled post went live on its network.
post.failed
A scheduled post could not be published; the payload carries the error.
post.cancelled
A pending post was cancelled or deleted before it published.
approval.requested
A post entered the review queue.
approval.approved
A reviewer approved a post.
approval.rejected
A reviewer rejected a post; the payload carries the note.
approval.changes_requested
A reviewer or client asked for changes to a post.
inbox.thread.created
A new conversation, comment, mention or review arrived in the inbox.
inbox.thread.assigned
An inbox thread was assigned to (or unassigned from) a team member.
brand.created
A brand was created in the agency.
connection.needs_reconnect
A connected network revoked or expired its authorisation and needs reconnecting.
Verifying a signature
X-Signature is t=<unix timestamp>,v1=<hex HMAC-SHA256 of "<t>.<raw body>" with your secret>. Reject a delivery whose t is more than 300 seconds from your clock, then compare v1 in constant time. The legacy X-Social-Signature header (HMAC of the body alone) is still sent for one release; move off it.
// Node.js
import { createHmac, timingSafeEqual } from 'node:crypto';
export function verify(header, rawBody, secret, tolerance = 300) {
const parts = Object.fromEntries(header.split(',').map((p) => p.trim().split('=')));
if (!/^\d+$/.test(parts.t ?? '') || !parts.v1) return false;
if (Math.abs(Math.floor(Date.now() / 1000) - Number(parts.t)) > tolerance) return false;
const expected = createHmac('sha256', secret).update(`${parts.t}.${rawBody}`).digest('hex');
const a = Buffer.from(expected, 'hex');
const b = Buffer.from(parts.v1, 'hex');
return a.length === b.length && timingSafeEqual(a, b);
}
Retries and the delivery ledger
Answer 2xx within ten seconds. Anything else is retried up to 5 times, waiting 1m, 5m, 15m, 1h between attempts. A delivery that exhausts its retries counts against the subscription; 5 in a row switch it off and email the workspace owners. Every attempt is on the ledger at GET /api/v1/webhooks/{id}/deliveries, and any delivery can be sent again with POST …/deliveries/{id}/redeliver.
MCP server
AI clients that speak the Model Context Protocol can point at https://social.iutils.net/mcp with the same bearer token. The tools mirror the API: list brands, analytics summary, scheduled posts, schedule a post, list and decide approvals, list and reply to inbox threads, list media and campaigns. The same scopes and brand permissions apply.
Changelog
2026-09-13
Scoped, expiring tokens. Cursor pagination on every list. One JSON error shape on every status. Idempotency keys. Per-token rate limit. New resources: brand create/update, scheduled post show/update/cancel/publish-now, media, approvals, inbox, campaigns, SmartLinks with statistics, per-network daily analytics and the post table, read-only paid media, report share links, team roster, webhook update/show, the delivery ledger and redelivery. Ten new webhook events and the timestamped X-Signature header. Webhook payloads now carry network, status and approval_status, and the network key uses the API spelling (linkedin, youtube, tiktok — previously linked_in, you_tube, tik_tok). OpenAPI 3.1 document. MCP tools for approvals, inbox, media and campaigns.
Earlier
Brands list, analytics summary, scheduled posts list and create, webhook subscriptions with post.published and post.failed signed with X-Social-Signature. All still work unchanged.