# SendFlow > One HTTP API to connect social accounts and publish across 15 networks: > Twitter/X, LinkedIn, Instagram, Facebook, Threads, YouTube, TikTok, > Pinterest, Reddit, Bluesky, Telegram, Discord, WhatsApp, Snapchat and > Google Business. Base URL: https://sendflow.com Auth: `Authorization: Bearer ` on every /v1 request. Interactive reference: https://sendflow.com/docs Machine-readable schema: https://sendflow.com/openapi.json ## Core model - **Organization** — the tenant. Every record belongs to exactly one. - **Profile** — groups the accounts one brand publishes through. - **Account** — a connected social account. Status is `connected`, `needs_reconnect` or `disconnected`. - **Post** — content plus one or more platform targets. Statuses: `draft`, `queued`, `scheduled`, `publishing`, `published`, `failed`, `partial_failure`, `manual_action_required`. - **Webhook** — an HTTPS endpoint notified when posts and accounts change. ## Publish a post ```bash curl -X POST https://sendflow.com/v1/posts \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "content": "Hello, world", "publishNow": true, "platforms": [ { "platform": "twitter", "accountId": "acc_123" }, { "platform": "linkedin", "accountId": "acc_456" } ] }' ``` `platforms` is required. Each entry needs `platform` and `accountId`; the account must belong to that platform or the request is rejected with 422. Instagram and Pinterest require media. Pass it per target: ```json { "platform": "instagram", "accountId": "acc_789", "platformSpecificData": { "imageUrl": "https://cdn.example/photo.jpg" } } ``` Schedule instead of publishing now by sending `scheduledFor` (ISO-8601) and omitting `publishNow`. Omit both to save a draft. ## Endpoints | Method | Path | Purpose | | --- | --- | --- | | GET/POST | /v1/profiles | List and create profiles | | GET/PATCH/DELETE | /v1/profiles/{id} | Read, rename, delete a profile | | GET | /v1/accounts | List connected accounts | | GET/DELETE | /v1/accounts/{id} | Read or disconnect an account | | GET | /v1/connect/{platform}?profileId= | Begin an account connection | | GET/POST | /v1/posts | List and create posts | | GET/PATCH/DELETE | /v1/posts/{id} | Read, edit, delete or cancel a post | | POST | /v1/posts/{id}/publish | Publish a draft or retry a failure | | GET/POST | /v1/webhooks | List and create webhook endpoints | | GET/PATCH/DELETE | /v1/webhooks/{id} | Manage one endpoint | | GET | /v1/webhooks/{id}/deliveries | Delivery history | | GET/POST | /v1/api-keys | List and create API keys | | DELETE | /v1/api-keys/{id} | Revoke a key | | POST | /v1/youtube/videos | Upload a video file to YouTube | | POST | /v1/tiktok/videos | Upload a video file to TikTok | | POST | /v1/pinterest/pins | Create a pin from an image file | | GET | /v1/me | Identify the calling organization | | GET | /health, /ready | Liveness and readiness | ## Behaviour worth knowing - **Idempotency** — send `Idempotency-Key` on `POST /v1/posts`. Replaying the same key within an organization returns the original post instead of publishing twice. - **Editing** — only `draft`, `scheduled` and `failed` posts can be edited or deleted. Anything that reached a platform is refused with 422. - **Token refresh** — access tokens are refreshed automatically. When refresh is no longer possible the account becomes `needs_reconnect` and an `account.needs_reconnect` webhook fires. - **Partial failure** — a multi-target post can succeed on some platforms and fail on others. Always read the per-target `results` array, not just `status`. - **Rate limits** — per organization per minute; a 429 carries `Retry-After`. - **Errors** — non-2xx responses are `{"success": false, "error": "...", "requestId": "..."}`. Quote the `requestId` when reporting a problem. ## Webhook verification Events: `post.published`, `post.failed`, `post.partial_failure`, `account.connected`, `account.needs_reconnect`. Each delivery carries `X-SocAPI-Event`, `X-SocAPI-Delivery`, `X-SocAPI-Timestamp` and `X-SocAPI-Signature`. Verify as: ```python expected = "sha256=" + hmac.new( secret.encode(), f"{timestamp}.".encode() + raw_body, hashlib.sha256 ).hexdigest() hmac.compare_digest(expected, received_signature) ``` ## Development stubs A platform without configured provider credentials runs against a local stub. Stubbed posts report success without reaching the network, and the account carries `metadata.mode == "development"`. Check that flag before trusting a `published` status in a test environment.