Hapi API documentation

One REST API to connect social accounts, publish and schedule posts, and read analytics across 16 platforms.

Base URL https://hapi.tools/v1

Running locally? Use http://localhost:3100/v1.

Introduction

Getting started

  1. Start a 15-day trial. Your workspace comes with labelled demo data and sandbox accounts so you can call the API right away.
  2. Create an API key in Dashboard → API keys. The secret is shown once — store it in an environment variable.
  3. Connect social accounts in Dashboard → Social accounts (OAuth, credentials, or a sandbox account).
  4. List your accounts, then publish with one request.
curl -X GET "https://hapi.tools/v1/accounts" \
  -H "Authorization: Bearer hapi_live_xxxxxxxxxxxxxxxx"

Authentication

Authenticate every request with a bearer token. Keys look like hapi_live_… and belong to one organization.

cURL
curl https://hapi.tools/v1/accounts \
  -H "Authorization: Bearer hapi_live_xxxxxxxx"
  • Scopes. READ keys can only call GET endpoints. READ_WRITE keys can create, update and delete.
  • Profile restrictions. A key can be limited to specific profiles; it then only sees accounts and posts within them.
  • Storage. We store a peppered SHA-256 hash of each key — never the key itself. Lost keys can't be recovered, only revoked and replaced.
  • Never call the API from a browser or mobile app with a secret key. Proxy requests through your backend.

Sandbox mode

Sandbox accounts let you build and test without platform credentials. They run every request through the same validation as live accounts (character limits, media rules, required options), but never contact the platform.

  • Sandbox targets finish with status sandbox_published and "sandbox": true. They are never reported as real publishes.
  • Include [sandbox:fail] in the content to force a failure and test your error handling.
  • Sandbox accounts count toward your plan's account limit and can be removed at any time.
Guides

Publishing posts

A post has one piece of content and one or more targets — the social accounts it goes to. Pass accountIds, platforms, or both (platforms then filters the accounts). Each target is validated against its platform's real limits before anything is published.

  • Omit scheduledAt (or send null) to publish immediately — the response contains the final per-target result.
  • Override text or supply required fields per platform with platformOptions, e.g. {"reddit": {"subreddit": "webdev", "title": "…"}}.
  • Attach media by mediaIds (from POST /v1/media) or public mediaUrls.
  • Send an Idempotency-Key header to make retries safe — the same key returns the original post.
  • firstComment is posted as a comment or self-reply on platforms that support it.
curl -X POST "https://hapi.tools/v1/posts" \
  -H "Authorization: Bearer hapi_live_xxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
  "accountIds": [
    "account_123"
  ],
  "content": "Our new product is live!",
  "platforms": [
    "instagram",
    "linkedin"
  ],
  "mediaUrls": [
    "https://example.com/image.jpg"
  ],
  "scheduledAt": null
}'

Scheduling

Set scheduledAt to an ISO-8601 timestamp with an offset (for example 2026-11-20T09:00:00-05:00). The post is stored with status scheduled and published by Hapi's scheduler within about 15 seconds of that time. Pass timezone (IANA name) to record the author's intent for display.

Scheduled posts can be edited or rescheduled with PATCH /v1/posts/:id until they start publishing. Drafts ("draft": true) are never published automatically.

curl -X POST "https://hapi.tools/v1/posts" \
  -H "Authorization: Bearer hapi_live_xxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
  "platforms": [
    "linkedin",
    "x"
  ],
  "content": "Webinar starts in one hour 👋",
  "scheduledAt": "2026-11-20T09:00:00-05:00",
  "timezone": "America/New_York"
}'

Analytics

Analytics are normalized into impressions, reach, likes, comments, shares, clicks and followers. Values come from each platform's API where it exposes them; some (Slack, Discord webhooks, LinkedIn member profiles) expose none, and Bluesky exposes followers and per-post counts only. Demo data is flagged so it's never mistaken for live numbers.

curl -X GET "https://hapi.tools/v1/analytics?from=2026-10-01&to=2026-10-31&platform=x" \
  -H "Authorization: Bearer hapi_live_xxxxxxxxxxxxxxxx"

Webhooks

Register an HTTPS endpoint to receive events as they happen. Each delivery is a JSON POST with these headers:

hapi-event
string
Event type, e.g. post.published.
hapi-delivery
string
Unique delivery id — use it to de-duplicate.
hapi-signature
string
t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>" using your endpoint secret>
EventWhen
post.scheduledA post was accepted with a future scheduledAt.
post.publishedEvery target published (sandbox targets count, flagged with sandbox: true).
post.partialSome targets published and some failed.
post.failedNo target could be published.
account.connectedA social account was connected or reconnected.
account.disconnectedA social account was removed.

Respond with any 2xx within 10 seconds. Failed deliveries are retried with exponential backoff (30s, 1m, 2m, 4m…) for up to 6 attempts, and can be retried manually from the dashboard or the API. Verify the signature before trusting a payload:

import crypto from "node:crypto";

export function verify(rawBody, header, secret, toleranceSec = 300) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  const t = Number(parts.t);
  if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
  const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1 ?? ""));
}

// Express: app.post("/hooks/hapi", express.raw({ type: "application/json" }), (req, res) => {
//   if (!verify(req.body.toString(), req.get("hapi-signature"), process.env.HAPI_WEBHOOK_SECRET)) return res.sendStatus(400);
//   res.sendStatus(200);
// });

AI agents (MCP)

Hapi runs a remote Model Context Protocol server at https://hapi.tools/mcp. Any MCP-capable agent (Claude, Codex, Gemini CLI, Cursor, VS Code Copilot, Windsurf) can publish, schedule and analyze with no code: add the server with your API key and ask in plain language. The fastest path is Dashboard → AI agents, which creates a key and fills it into these snippets.

Run in your terminal
claude mcp add --transport http hapi https://hapi.tools/mcp \
  --header "Authorization: Bearer YOUR_HAPI_API_KEY"

Then run /mcp inside Claude Code to confirm it's connected.

Transport is stateless Streamable HTTP; authenticate with the same Authorization: Bearer key as the REST API. Rate limits, plan limits, profile restrictions and the audit log all apply. Write tools need a READ_WRITE key, so give reporting agents a READ key.

ToolWhat it doesKey
get_workspacePlan, limits, usage this month and whether the key can writeread
list_platformsEvery platform's limits, features and required optionsread
list_accountsConnected accounts, including which are sandboxread
create_postPublish now, schedule or draft across accounts in one callread-write
list_postsPosts filtered by status, platform or dateread
get_postPer-platform result, live URL or error for one postread
update_postEdit a draft or scheduled postread-write
delete_postDelete or cancel a postread-write
retry_postRetry only the targets that failedread-write
get_analyticsDaily followers, impressions, engagement and clicksread

No MCP support? Point the agent at /llms.txt, a plain-text guide to the REST API written for LLMs, or define your own tools over the endpoints below. ChatGPT and claude.ai web connectors need OAuth sign-in, which is coming soon.

Reference

API reference

All endpoints accept and return JSON (except media upload, which takes multipart/form-data). Timestamps are ISO-8601 in UTC. List endpoints paginate with page and limit.

List accounts

GET/v1/accounts

Returns the social accounts this key can access. status is active, expired (reconnect needed), revoked or error. sandbox: true marks simulated accounts.

curl -X GET "https://hapi.tools/v1/accounts" \
  -H "Authorization: Bearer hapi_live_xxxxxxxxxxxxxxxx"
JSON
{
  "data": [
    {
      "id": "account_123",
      "platform": "linkedin",
      "username": "acme-inc",
      "displayName": "Acme Inc.",
      "avatarUrl": null,
      "status": "active",
      "sandbox": false,
      "profileId": "prof_1",
      "connectedAt": "2026-10-02T10:00:00.000Z"
    }
  ]
}

Create post

POST/v1/posts
content
string
Post text. Validated per platform (e.g. 280 for X, 300 graphemes for Bluesky).
accountIds
string[]
Accounts to publish to.
platforms
string[]
Publish to all active accounts on these platforms, or filter accountIds.
mediaIds
string[]
Media from POST /v1/media, in order.
mediaUrls
string[]
Public URLs to attach.
scheduledAt
string | null
ISO-8601 with offset. Null/omitted publishes now.
draft
boolean
Save without publishing or validating.
timezone
string
IANA timezone for display.
firstComment
string
Comment/reply added after publishing where supported.
platformOptions
object
Per-platform {content, title, subreddit, boardId}.
idempotencyKey
string
Same as the Idempotency-Key header.

Returns 201 with the post. When publishing immediately, status is the final outcome: published, partial or failed; inspect targets[].error for failures. Validation failures return 422 with one entry per problem.

curl -X POST "https://hapi.tools/v1/posts" \
  -H "Authorization: Bearer hapi_live_xxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
  "accountIds": [
    "account_123"
  ],
  "content": "Our new product is live!",
  "platforms": [
    "instagram",
    "linkedin"
  ],
  "mediaUrls": [
    "https://example.com/image.jpg"
  ],
  "scheduledAt": null
}'
JSON
{
  "id": "cmh2k8x0p0001",
  "content": "Our new product is live!",
  "status": "published",
  "scheduledAt": null,
  "publishedAt": "2026-11-01T14:02:11.000Z",
  "timezone": "UTC",
  "firstComment": null,
  "source": "api",
  "isDemo": false,
  "media": [
    {
      "id": "cmh2k7q1z0000",
      "url": "https://example.com/image.jpg",
      "kind": "image"
    }
  ],
  "targets": [
    {
      "accountId": "account_123",
      "platform": "linkedin",
      "username": "acme-inc",
      "status": "published",
      "sandbox": false,
      "platformPostId": "urn:li:share:7251…",
      "url": "https://www.linkedin.com/feed/update/urn:li:share:7251…/",
      "error": null,
      "publishedAt": "2026-11-01T14:02:11.000Z"
    },
    {
      "accountId": "account_456",
      "platform": "instagram",
      "username": "@instagram_sandbox",
      "status": "sandbox_published",
      "sandbox": true,
      "platformPostId": "sbx_instagram_9fK2…",
      "url": null,
      "error": null,
      "publishedAt": "2026-11-01T14:02:10.000Z"
    }
  ],
  "createdAt": "2026-11-01T14:02:09.000Z",
  "updatedAt": "2026-11-01T14:02:11.000Z"
}

List posts

GET/v1/posts
status
string
draft | scheduled | publishing | published | partial | failed
from, to
date
Filter by scheduled (or created) time.
platform
string
Only posts targeting this platform.
page, limit
number
Pagination. limit max 100.
curl -X GET "https://hapi.tools/v1/posts?status=scheduled&limit=20" \
  -H "Authorization: Bearer hapi_live_xxxxxxxxxxxxxxxx"
JSON
{
  "data": [
    "…post objects…"
  ],
  "pagination": {
    "page": 1,
    "limit": 20,
    "total": 42,
    "hasMore": true
  }
}

Retrieve, update or delete a post

GET/v1/posts/{id}

GET /v1/posts/{id} returns the post with per-target results.

PATCH /v1/posts/{id} accepts any create field. Only draft, scheduled and failed posts can be edited; non-draft updates are re-validated and need a future scheduledAt.

DELETE /v1/posts/{id} returns 204. It removes the post from Hapi; content already live on a platform stays there.

curl -X PATCH "https://hapi.tools/v1/posts/cmh2k8x0p0001" \
  -H "Authorization: Bearer hapi_live_xxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
  "scheduledAt": "2026-11-21T15:00:00Z"
}'

Retry a post

POST/v1/posts/{id}/retry

Re-publishes only the failed targets of a failed or partial post. Successful targets are never published twice.

curl -X POST "https://hapi.tools/v1/posts/cmh2k8x0p0001/retry" \
  -H "Authorization: Bearer hapi_live_xxxxxxxxxxxxxxxx"

Upload media

POST/v1/media

Send the file as multipart field file. Accepted: JPEG, PNG, WebP (8 MB), GIF (15 MB), MP4 and MOV (512 MB). Files are verified by content, not extension.

curl -X POST "https://hapi.tools/v1/media" \
  -H "Authorization: Bearer hapi_live_xxxxxxxxxxxxxxxx" \
  -F "file=@./launch.jpg"
JSON
{
  "id": "cmh2k7q1z0000",
  "url": "https://hapi.tools/api/media/…/a1b2.jpg",
  "kind": "image",
  "mimeType": "image/jpeg",
  "sizeBytes": 482113
}

Get analytics

GET/v1/analytics
from, to
date
Defaults to the last 30 days.
platform
string
Only this platform.
accountId
string
Only this account.
curl -X GET "https://hapi.tools/v1/analytics?from=2026-10-01&to=2026-10-31" \
  -H "Authorization: Bearer hapi_live_xxxxxxxxxxxxxxxx"
JSON
{
  "data": {
    "isDemo": false,
    "totals": {
      "impressions": 48210,
      "reach": 34711,
      "likes": 2169,
      "comments": 289,
      "shares": 193,
      "clicks": 578,
      "followers": 12840,
      "engagement": 2651,
      "engagementRate": 0.055
    },
    "series": [
      "…daily rows…"
    ],
    "platforms": [
      "…per platform…"
    ]
  }
}

List platform capabilities

GET/v1/platforms

Machine-readable limits and capabilities for every platform, plus whether live connections are enabled in this deployment. Use it to drive your own composer UI.

curl -X GET "https://hapi.tools/v1/platforms" \
  -H "Authorization: Bearer hapi_live_xxxxxxxxxxxxxxxx"

Webhook endpoints

POST/v1/webhooks

GET /v1/webhooks lists endpoints. POST creates one and returns its signing secret once. DELETE /v1/webhooks/{id} removes it. POST /v1/webhooks/deliveries/{id}/retry re-sends a delivery.

curl -X POST "https://hapi.tools/v1/webhooks" \
  -H "Authorization: Bearer hapi_live_xxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
  "url": "https://example.com/hooks/hapi",
  "events": [
    "post.published",
    "post.failed"
  ]
}'
JSON
{
  "id": "wh_1",
  "url": "https://example.com/hooks/hapi",
  "events": [
    "post.published",
    "post.failed"
  ],
  "enabled": true,
  "secret": "whsec_…"
}
Platform

Platform capabilities

Platforms don't all support the same things. Hapi enforces each platform's actual rules instead of pretending otherwise.

PlatformConnect viaAccessCharsImagesText-onlyVideo1st commentAnalyticsNotes
InstagramOAuth 2.0App review2,20010○●●●Professional (Business or Creator) accounts only, via the Instagram Graph API.
TikTokOAuth 2.0App review2,20035○●○●Content Posting API. Video or photo carousel required — no text-only posts.
XOAuth 2.0Open2804●●●●OAuth 2.0 with PKCE; posting uses POST /2/tweets.
LinkedInOAuth 2.0Open3,00020●●●●Members via "Share on LinkedIn" (w_member_social). Company pages require Community Management API approval.
FacebookOAuth 2.0App review63,20610●●●●Facebook Pages only — personal profiles cannot be published to via the API.
ThreadsOAuth 2.0App review50020●●●●Threads API: 250 published posts per profile per 24 hours.
YouTubeOAuth 2.0App review5,0000○●○●Video uploads only (including Shorts). Content becomes the description.
PinterestOAuth 2.0App review5005○●○●Every Pin needs an image or video and a destination board.
BlueskyCredentialsOpen3004●●●○Connects with an app password over the AT Protocol — no developer app needed.
RedditOAuth 2.0Open40,0001●○●○Each post targets one subreddit and needs a title. Subreddit rules still apply.
SnapchatOAuth 2.0Partner only1601○●○○Publishing to Public Profiles (Stories, Spotlight) requires Snap partner approval. Sandbox only until approved.
DiscordCredentialsOpen2,00010●●○○Posts to a channel through an incoming webhook.
WhatsAppCredentialsApp review4,0961●●○○WhatsApp is a messaging channel — the Cloud API sends messages to recipients, it has no public feed.
TelegramCredentialsOpen4,09610●●○○Bot API. Captions on media are limited to 1,024 characters.
SlackCredentialsOpen40,00010●○○○Posts through a Slack incoming webhook. Images are embedded by URL.
Google Business ProfileOAuth 2.0App review1,5001●○○●Local posts (updates, offers, events). Requires Business Profile API access approval from Google.

Rate limits

Limits apply per API key over a one-minute window and depend on your plan:

PlanRequests / minuteAccountsPosts / month
Trial1205300
Growth300255,000
Pro1,20010025,000
Scale6,000UnlimitedUnlimited

Every response includes X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (seconds until the window resets). Over the limit you'll get 429 rate_limited with a Retry-After header. Platforms also enforce their own quotas (for example, Instagram's 100 API posts per 24 hours); those surface as target errors with code platform_rate_limited.

Error codes

Errors use one envelope. details is present for validation errors and lists each problem.

JSON
{
  "error": {
    "code": "validation_error",
    "message": "Post failed platform validation",
    "details": [
      {
        "accountId": "account_789",
        "platform": "x",
        "message": "X: 312/280 characters"
      }
    ]
  }
}
CodeHTTPMeaning
bad_request400Malformed request, e.g. invalid JSON or a missing multipart file.
unauthorized401Missing, invalid, expired or revoked API key.
plan_limit402Your plan's account or monthly post limit was reached.
forbidden403Key is read-only, or the resource is outside the key's profiles.
not_found404The resource doesn't exist in your organization.
conflict409The resource is in a state that doesn't allow this action (e.g. editing a published post).
validation_error422Body failed schema or platform validation. `details` lists each issue.
rate_limited429Too many requests. Wait for `Retry-After` seconds.
internal_error500Unexpected server error. Safe to retry with an Idempotency-Key.
Beta

Messaging API

BetaNot yet available in the public API. Contact sales@hapi.tools to join the beta.

Send and receive direct messages on platforms with messaging APIs (Instagram, Facebook Messenger, WhatsApp Cloud API).

Beta

Comments API

BetaNot yet available in the public API. Contact sales@hapi.tools to join the beta.

Read, reply to, hide and delete comments on published posts.

Beta

Inbox API

BetaNot yet available in the public API. Contact sales@hapi.tools to join the beta.

A unified, normalized stream of comments, mentions and messages across accounts.

Beta

Ads API

BetaNot yet available in the public API. Contact sales@hapi.tools to join the beta.

Boost posts and read paid performance alongside organic analytics.

Legal

Security

OAuth tokens and platform credentials are encrypted at rest with AES-256-GCM and only decrypted server-side at publish time. API keys are stored as peppered hashes. Webhook payloads are HMAC-signed. All tenant data is isolated per organization and every privileged action is written to an audit log. Report vulnerabilities to support@hapi.tools.

Terms of service

Placeholder — the full terms of service will be published here before general availability. You remain responsible for complying with each social platform's developer terms.

Privacy policy

Placeholder — the full privacy policy will be published here. We only request the platform permissions needed to publish and read analytics, and you can disconnect accounts at any time.