Use Hermoso as your backend
If you build a product for your own customers, you can run publishing, scheduling, inboxes and creative on Hermoso through the REST API. Each of your customers gets a brand. They connect their own social accounts from a link you hand them, and they never need a Hermoso login.
What this page covers is available today. Our Terms of Service allow you to build on the API and offer it to your own customers. You are responsible for your customers following each platform's own terms, and our acceptable use rules apply to everything that runs through your key.
The flow, end to end
- Create a key in the app on the API, MCP & CLI tab, under Terminal & API keys, and send it as
Authorization: Bearer hmk_...from your server. The API refuses browser calls on purpose. - Register where your customers come back to.
PUT /v1/connect-redirectswith{"urls": ["https://app.yourproduct.com/hermoso"]}. https only (http://localhostis accepted while you test). A link can only send people back to an address under one of these. - Create a brand per customer with
POST /v1/tools/create_brand.GET /v1/brandslists the ids. Every call after this names the brand with theX-Hermoso-Userheader. - Connect a webhook on the brand (optional, recommended):
POST /v1/tools/connect_connectorwith{"provider": "webhook", "webhookUrl": "https://..."}. The signing secret comes back once. Then choose the events withset_alert_events(see Webhooks). - Create a connect link for the platform your customer wants to connect:
The answer carriesPOST /v1/connect-links X-Hermoso-User: <brand id> { "provider": "tiktok", "redirect_url": "https://app.yourproduct.com/hermoso/done", "state": "your-customer-id", "expires_in_minutes": 60 }url. Show it to your customer as a button or open it in a new tab. - Your customer connects. They see a short Hermoso page, accept Hermoso's Terms and Privacy Policy, sign in on the platform's own consent screen, and land back on your
redirect_url. - You find out it connected from the query string on your redirect, from the
account.connectedwebhook, or by pollingGET /v1/connect-links/{id}. - Publish with
POST /v1/postson that brand, now or scheduled.
Connect links
Providers
meta (Facebook Pages and the Instagram accounts linked to them), instagram (an Instagram account on its own login, no Facebook Page needed), tiktok, youtube, linkedin, linkedin_community (lets the brand comment as its LinkedIn Page), x, pinterest, threads, google_business, and the ad platforms. A provider that is not available yet is refused with the list. Accounts that connect with a pasted key, such as Bluesky and Telegram, are connected with POST /v1/tools/connect_connector instead.
What comes back on your redirect
Your own query string is kept. We add these, and a parameter of yours with the same name is overwritten:
| Parameter | Value |
|---|---|
connect_status | success, cancelled or error |
provider | The provider the link was for |
brand | The brand id |
link_id | The link's id |
account | The connected account's handle or name, on success |
error_code | On an error: access_denied, provider_error, connection_failed, different_browser, host_connection_missing |
state | The state you passed |
Treat the redirect as a hint for your UI, not as proof: confirm with GET /v1/connect-links/{id} or the webhook before you rely on it.
Rules a link follows
- One provider, one brand. It can attach the end user's own account to that brand and nothing else.
- It expires after
expires_in_minutes(default 60, at most 2880). A cancelled or failed attempt can be retried with the same link until then. Once an account connects, the link is spent. - It is a bearer link: anyone holding it can connect an account to that brand until it is used or expires. Hand it to the customer it is for, and cancel one you no longer need with
DELETE /v1/connect-links/{id}. - The consent has to finish in the browser that opened the link.
Whose name is on the consent screen
The platform's consent screen shows Hermoso, because the connection is made through Hermoso's app on that platform. Our page tells your customer this before they reach it. Your customer can remove that access at any time in the platform's own settings.
Your customers and Hermoso
On the connect page your customer accepts Hermoso's Terms of Service and Privacy Policy, so they are also a Hermoso user under those terms.
Choosing Pages after a Facebook or LinkedIn connect
A Facebook login can reach many Pages, and a LinkedIn login reaches the person's profile and every Page they manage. Nothing is posted to until the brand has a choice saved. The link's creator owns that choice: call list_connector_accounts and then set_connector_accounts through POST /v1/tools/{name}. account_selection_required on the link (and in the account.connected event) tells you when this step applies.
Connections
GET /v1/connectionslists every account connected to the brand, every account on a channel that holds several, and whether one needs reconnecting.DELETE /v1/connections/{provider}removes a connection from that brand. Pass?account=to remove one account from a channel that holds several. It does not revoke Hermoso's access at the platform: the account holder does that in the platform's settings.GET /v1/channelsdescribes what each channel supports (caption limits, carousels, visibilities).
Webhooks
A brand's webhook receives signed events (Standard Webhooks: webhook-id, webhook-timestamp, webhook-signature with v1,<base64 HMAC-SHA256 of "id.timestamp.body">). A delivery that fails with a 5xx, 408, 425, 429 or a timeout is tried up to seven times over about 16 hours, and every event carries a stable webhook-id so you can drop a duplicate. Choose groups with set_alert_events; rotate the secret with rotate_webhook_signing_secret; read the delivery log with list_alert_settings.
| Group | Events | On by default |
|---|---|---|
connections | account.connected, account.disconnected | Yes |
inbox | comment.received, message.received | No, name it to receive it |
posts | post.published, post.partial, post.failed | Yes |
renders, approvals, watch, credits, leads | Renders, drafts awaiting approval, competitor finds, low credits, new leads | Yes |
Every payload names the brand. account.connected also carries provider, account, link_id, your state and account_selection_required.
Comments and DMs
comment.received and message.received come from what the platform pushes to us: Facebook Page comments, Messenger, Instagram comments and Instagram Direct. Each carries the platform, the ids you need to answer (comment_id or message_id), the text and the sender. A brand's own comments are not sent. For an Instagram account connected with the instagram provider this is on as soon as it connects. For a Facebook Page, call subscribe_meta_webhooks once with the fields feed and messages. Other platforms do not push comments or messages to us; read them with list_inbox.
First comment
Send first_comment on POST /v1/posts (or firstComment on schedule_post, post_to_meta and post_to_linkedin_page) and the account comments on its own post right after it publishes.
- Supported: Facebook Pages, Instagram, and LinkedIn company Pages. LinkedIn needs its comments authorization on the brand (connect
linkedin_community). - Not supported: TikTok (TikTok finishes processing a post after accepting it and its comment API needs a separate authorization; add one later with
comment_on_tiktok_video), a LinkedIn personal profile, X, Threads, Bluesky, YouTube, Pinterest, Telegram, Reddit and Google Business Profile. A post that includes one of these still publishes there, and that channel's result says it went without a first comment. - Up to 2,000 characters. A longer one is refused before anything publishes, never cut.
- If the comment fails, the post stays live and the channel's result says why.
Limits
Rate limits are per API key and are sent on every response as X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset. Each platform's own posting limits still apply. Plans, prices and limits can change; the current ones are on the pricing page and in Credits & costs.